mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a65b646df | ||
|
|
24c715aae9 | ||
|
|
9ab85768bd | ||
|
|
16458d96e7 | ||
|
|
b307dd17af | ||
|
|
313552352c | ||
|
|
543ac7c649 | ||
|
|
dacff76694 | ||
|
|
c134169ea1 | ||
|
|
e252945047 | ||
|
|
f8cfcaab20 | ||
|
|
528237a239 | ||
|
|
91aefe25c4 | ||
|
|
a8c11097d9 | ||
|
|
92ba4a3ae9 | ||
|
|
7c5d382010 | ||
|
|
f8fbaefd86 | ||
|
|
181b37296a | ||
|
|
2ab61cfa12 | ||
|
|
1b0e9b14ef | ||
|
|
2aacc9266f | ||
|
|
d648d477bb | ||
|
|
6f91c1a1d3 | ||
|
|
aa1aca24b7 | ||
|
|
6a0867172f | ||
|
|
f025a4b2fb | ||
|
|
8871f36a92 | ||
|
|
f17490edad | ||
|
|
b360e782c6 | ||
|
|
8d94324dd6 | ||
|
|
2818609412 | ||
|
|
270a2e69d4 | ||
|
|
d1d9762e29 | ||
|
|
1666e8b127 | ||
|
|
08dfad208b | ||
|
|
b5921f95f3 | ||
|
|
2063bc3db3 | ||
|
|
4380016d52 | ||
|
|
5e3ceabe46 | ||
|
|
8e7936275b | ||
|
|
4b967239fa | ||
|
|
92945c384c | ||
|
|
79d93c4ecf | ||
|
|
76b6f25b0c | ||
|
|
aadce4890a | ||
|
|
0e95a98fc2 | ||
|
|
9483437e8f | ||
|
|
59542f8aef | ||
|
|
e29f7332f5 | ||
|
|
f8640feafe | ||
|
|
e94e9e2be4 | ||
|
|
4053aac365 | ||
|
|
a5fa6c7aef | ||
|
|
97263894d1 | ||
|
|
1885580958 | ||
|
|
1167b470bb | ||
|
|
7600506d6d | ||
|
|
86bad866a0 | ||
|
|
2f1a15cf7e | ||
|
|
52b0e1870f | ||
|
|
9b181c1e0d | ||
|
|
100f2dc45e | ||
|
|
b247c3d477 | ||
|
|
76ee82b258 | ||
|
|
e8fcd29669 | ||
|
|
8dd16ecea4 | ||
|
|
e9c0bcd877 | ||
|
|
75ed4b52a6 | ||
|
|
71635c00df | ||
|
|
1810af5483 | ||
|
|
b07835dfd5 | ||
|
|
4c33aa2aae | ||
|
|
3c255640cb | ||
|
|
3d08ba9ebc | ||
|
|
f64482500e | ||
|
|
215902f192 | ||
|
|
3e9c3a069d | ||
|
|
841fb48479 | ||
|
|
df8e41925f | ||
|
|
6b0994a990 | ||
|
|
7dd616e891 | ||
|
|
c672edbe4d | ||
|
|
687ecc7097 | ||
|
|
b8882b4826 | ||
|
|
51de0b38a4 | ||
|
|
e0309c0482 | ||
|
|
5dbe86869d | ||
|
|
14a11279c7 | ||
|
|
df0ce42377 | ||
|
|
4c03411405 | ||
|
|
f020e18238 | ||
|
|
629bd4aff9 | ||
|
|
f20825a66c | ||
|
|
f098ca0d02 | ||
|
|
1f96f74f4d | ||
|
|
7a3a98c27a | ||
|
|
1130448cb9 | ||
|
|
d388e99c0e | ||
|
|
2baea9a6b4 | ||
|
|
0629625a9a | ||
|
|
a2d0acc761 | ||
|
|
28a6bce90f | ||
|
|
9058192ffe | ||
|
|
465b358271 | ||
|
|
7de585fe1d | ||
|
|
8479730c95 | ||
|
|
7102e2df4c | ||
|
|
c3bd99ff93 | ||
|
|
c560405a46 | ||
|
|
0c0fb28ccc | ||
|
|
a33fce942c | ||
|
|
369cd40ee5 | ||
|
|
577b431a41 | ||
|
|
75cf8acd33 | ||
|
|
d70983962b | ||
|
|
ff440984b0 | ||
|
|
c631155be7 | ||
|
|
6038930755 | ||
|
|
9edc119c62 | ||
|
|
269a94bf03 | ||
|
|
7f3e4d7468 | ||
|
|
eb89891cdd | ||
|
|
038b068370 | ||
|
|
d2017a59de | ||
|
|
3435b56a84 | ||
|
|
a812558d2d | ||
|
|
aefd85455e | ||
|
|
e42f1347b7 | ||
|
|
c7442a03d1 | ||
|
|
1eda8bdd9d | ||
|
|
c4d0628bdb | ||
|
|
d51ae66242 | ||
|
|
121dde6b8b | ||
|
|
98081b067d | ||
|
|
8cc9345b42 | ||
|
|
f7528365b0 | ||
|
|
7baa1a345e | ||
|
|
acf7f4fd52 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
.DS_Store
|
||||
Build
|
||||
Dist
|
||||
santa-*
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
Santa.xcodeproj/project.xcworkspace
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
language: objective-c
|
||||
cache: cocoapods
|
||||
cache:
|
||||
- bundler
|
||||
- cocoapods
|
||||
sudo: false
|
||||
osx_image: xcode7
|
||||
|
||||
before_install:
|
||||
- gem install activesupport
|
||||
- gem install cocoapods xcpretty
|
||||
- pod setup >/dev/null
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -82,3 +82,4 @@ myclean:
|
||||
@rm -f com.google.santad.plist
|
||||
@rm -f com.google.santagui.plist
|
||||
@rm -f install.sh
|
||||
@rm -f uninstall.sh
|
||||
|
||||
@@ -18,7 +18,8 @@ sleep 1
|
||||
sleep 1
|
||||
|
||||
# Create hopefully useful symlink for santactl
|
||||
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "$user" ]] && exit 0
|
||||
|
||||
@@ -36,6 +36,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
# Copy new files.
|
||||
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
|
||||
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
|
||||
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
|
||||
|
||||
26
Conf/uninstall.sh
Executable file
26
Conf/uninstall.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Uninstalls Santa from the boot volume, clearing up everything but logs/configs.
|
||||
# Unloads the kernel extension, services, and deletes component files.
|
||||
# If a user is logged in, also unloads the GUI agent.
|
||||
|
||||
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
# and to clean out the log config, although it won't write after wiping the binary
|
||||
/usr/bin/killall -HUP syslogd
|
||||
# delete artifacts on-disk
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /usr/local/bin/santactl # just a symlink
|
||||
#uncomment to remove the config file and all databases, log files
|
||||
#/bin/rm -rf /var/db/santa
|
||||
#/bin/rm -f /var/log/santa*
|
||||
exit 0
|
||||
65
Podfile
65
Podfile
@@ -2,47 +2,48 @@ platform :osx, "10.9"
|
||||
|
||||
inhibit_all_warnings!
|
||||
|
||||
def mol_pods
|
||||
target :Santa do
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
end
|
||||
|
||||
def fmdb_pod
|
||||
target :santad do
|
||||
pod 'FMDB'
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
end
|
||||
|
||||
# This is necessary to get FMDB to not NSLog stuff.
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if config.name != 'Release' then
|
||||
break
|
||||
end
|
||||
target :santactl do
|
||||
pod 'FMDB'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'MOLFCMClient'
|
||||
end
|
||||
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
|
||||
target :LogicTests do
|
||||
pod 'FMDB'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'OCMock'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if config.name != 'Release' then
|
||||
break
|
||||
end
|
||||
|
||||
# This is necessary to get FMDB to not NSLog stuff.
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
|
||||
|
||||
# Enable more compiler optimizations.
|
||||
config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 'fast'
|
||||
config.build_settings['LLVM_LTO'] = 'YES'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
target :Santa do
|
||||
mol_pods
|
||||
end
|
||||
|
||||
target :santad do
|
||||
mol_pods
|
||||
fmdb_pod
|
||||
end
|
||||
|
||||
target :santactl do
|
||||
mol_pods
|
||||
fmdb_pod
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
end
|
||||
|
||||
target :LogicTests do
|
||||
mol_pods
|
||||
fmdb_pod
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'OCMock'
|
||||
end
|
||||
|
||||
22
Podfile.lock
22
Podfile.lock
@@ -2,27 +2,31 @@ PODS:
|
||||
- FMDB (2.6.2):
|
||||
- FMDB/standard (= 2.6.2)
|
||||
- FMDB/standard (2.6.2)
|
||||
- MOLAuthenticatingURLSession (1.6):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- MOLCertificate (1.4)
|
||||
- MOLAuthenticatingURLSession (2.1):
|
||||
- MOLCertificate (~> 1.5)
|
||||
- MOLCertificate (1.5)
|
||||
- MOLCodesignChecker (1.5):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- OCMock (3.3)
|
||||
- MOLFCMClient (1.1):
|
||||
- MOLAuthenticatingURLSession (~> 2.1)
|
||||
- OCMock (3.3.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- MOLFCMClient
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
|
||||
MOLAuthenticatingURLSession: f956240458fb24b61e5607d735948dc9babfb4e3
|
||||
MOLCertificate: da0bfeb5fa968bb4ac284569fa3f7d5f8f7abe23
|
||||
MOLAuthenticatingURLSession: 2f0fd35f641bc857ee1b026021dbd759955adaa3
|
||||
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
|
||||
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
|
||||
OCMock: d68685bde31f69cb61d518dcb39269080c78b5ed
|
||||
MOLFCMClient: f1684219facbffdb060ff4ab18b1825bcd4c75bc
|
||||
OCMock: f3f61e6eaa16038c30caa5798c5e49d3307b6f22
|
||||
|
||||
PODFILE CHECKSUM: 3a8673334ffd78cdbd6576c85e6635248eb1b504
|
||||
PODFILE CHECKSUM: b20628b5933f54525daf0dcc5534512b1cb134c8
|
||||
|
||||
COCOAPODS: 1.0.0
|
||||
COCOAPODS: 1.0.1
|
||||
|
||||
93
README.md
93
README.md
@@ -7,7 +7,7 @@ Santa [](h
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for OS X. It consists of
|
||||
Santa is a binary whitelisting/blacklisting system for macOS. It consists of
|
||||
a kernel extension that monitors for executions, a userland daemon that makes
|
||||
execution decisions based on the contents of a SQLite database, a GUI agent that
|
||||
notifies the user in case of a block decision and a command-line utility for
|
||||
@@ -16,25 +16,53 @@ managing the system and synchronizing the database with a server.
|
||||
Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
|
||||
and finishing up a security audit.
|
||||
|
||||
Santa is named because it keeps track of binaries that are naughty and nice.
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
Santa is a project of Google's Macintosh Operations Team.
|
||||
|
||||
Features
|
||||
Admin-Related Features
|
||||
========
|
||||
|
||||
* Multiple modes: MONITOR and LOCKDOWN. In MONITOR mode all binaries except
|
||||
those marked as blacklisted will be allowed to run, whilst being logged and
|
||||
recorded in the database. In LOCKDOWN mode, only whitelisted binaries are
|
||||
* Multiple modes: In the default MONITOR mode, all binaries except
|
||||
those marked as blacklisted will be allowed to run, whilst being logged and recorded in the events database. In LOCKDOWN mode, only whitelisted binaries are
|
||||
allowed to run.
|
||||
|
||||
* Codesign listing: Binaries can be whitelisted/blacklisted by their signing
|
||||
certificate, so you can trust/block all binaries by a given publisher. The
|
||||
binary will only be whitelisted by certificate if its signature validates
|
||||
correctly. However, a decision for a binary will override a decision for a
|
||||
certificate; i.e. you can whitelist a certificate while blacklisting a binary
|
||||
signed by that certificate or vice-versa.
|
||||
* Event logging: When the kext is loaded, all binary launches are logged.
|
||||
When in either mode, all unknown or denied binaries are stored in the database to enable later aggregation.
|
||||
|
||||
* Certificate-based rules, with override levels: Instead of relying on a binaries hash (or 'fingerprint'), executables can be whitelisted/blacklisted by their signing
|
||||
certificate. You can therefore trust/block all binaries by a given publisher that were signed with that cert across version updates. A
|
||||
binary can only be whitelisted by its certificate if its signature validates
|
||||
correctly, but a rule for a binaries fingerprint will override a decision for a
|
||||
certificate; i.e. you can whitelist a certificate while blacklisting a binary
|
||||
signed with that certificate, or vice-versa.
|
||||
|
||||
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature as Managed Client for OS X's (the precursor to configuration profiles, which used the same implementation mechanism) Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and doesn't rely on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precendence.
|
||||
|
||||
* Failsafe cert rules: You cannot put in a deny rule that would block the certificate used to sign launchd, a.k.a. pid 1, and therefore all components used in macOS. The binaries in every OS update (and in some cases entire new versions) are therefore auto-whitelisted. This does not affect binaries from Apple's App Store, which use various certs that change regularly for common apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct separate cert than other Google apps.
|
||||
|
||||
Intentions and Expectations
|
||||
===========================
|
||||
No single system or process will stop *all* attacks, or provide 100% security.
|
||||
Santa is written with the intention of helping protect users from themselves.
|
||||
People often download malware and trust it, giving the malware credentials, or
|
||||
allowing unknown software to exfiltrate more data about your system. As a
|
||||
centrally managed component, Santa can help stop the spread of malware among a
|
||||
larger fleet of machines. Independently, Santa can aid in analyzing what is
|
||||
running on your computer.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to protect
|
||||
hosts in whatever other ways you see fit.
|
||||
|
||||
Get Help
|
||||
========
|
||||
|
||||
If you have questions or otherwise need help getting started, the
|
||||
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
|
||||
great place. Please consult the [wiki](https://github.com/google/santa/wiki) and [issues](https://github.com/google/santa/issues) as well.
|
||||
|
||||
Security and Performance-Related Features
|
||||
============
|
||||
* In-kernel caching: whitelisted binaries are cached in the kernel so the
|
||||
processing required to make a request is only done if the binary
|
||||
isn't already cached.
|
||||
@@ -44,42 +72,17 @@ daemon, the GUI agent and the command-line utility) communicate with each other
|
||||
using XPC and check that their signing certificates are identical before any
|
||||
communication is accepted.
|
||||
|
||||
* Event logging: all executions processed by the userland agent are logged and
|
||||
all unknown or denied binaries are also stored in the database for upload to a
|
||||
server.
|
||||
|
||||
* Kext uses only KPIs: the kernel extension only uses provided kernel
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
|
||||
Intentions and Expectations
|
||||
===========================
|
||||
No single system or process will stop *all* attacks, or provide 100% security.
|
||||
Santa is written with the intention of helping protect users from themselves.
|
||||
People often download malware and trust it, giving the malware credentials, or
|
||||
allowing unknown software to exfiltrate more data about your system. As a
|
||||
centrally managed component, Santa can help stop the spread of malware among a
|
||||
larger fleet of machines. Additionally, Santa can aid in analyzing what is
|
||||
running in your fleet.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to protect
|
||||
hosts in whatever other ways you see fit.
|
||||
|
||||
Get Help
|
||||
========
|
||||
|
||||
If you have questions or need help getting started, the
|
||||
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is the
|
||||
best place to start.
|
||||
|
||||
Known Issues
|
||||
============
|
||||
Santa is not yet a 1.0 and we have some known issues to be aware of:
|
||||
|
||||
* Santa only blocks execution (execve and variants), it doesn't protect against
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been replaced or
|
||||
libraries loaded using `DYLD_INSERT_LIBRARIES`. We are working on also protecting
|
||||
against these avenues of attack.
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been replaced, or
|
||||
libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version 0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were exploited in some versions of macOS. We are working on also protecting against similar avenues of attack.
|
||||
|
||||
* Kext communication security: the kext will only accept a connection from a
|
||||
single client at a time and said client must be running as root. We haven't yet
|
||||
@@ -89,9 +92,8 @@ found a good way to ensure the kext only accepts connections from a valid client
|
||||
only the root user can read/write it. We're considering approaches to secure
|
||||
this further.
|
||||
|
||||
* Sync client: the command-line client includes a command to synchronize with a
|
||||
management server, including the uploading of events that have occurred on the
|
||||
machine and to download new rules. We're still very heavily working on this
|
||||
* Sync client: The `santactl` command-line client includes a flag to synchronize with a management server, which uploads events that have occurred on the
|
||||
machine and downloads new rules. We're still very heavily working on this
|
||||
server (which is AppEngine-based and will be open-sourced in the future), so the
|
||||
sync client code is unfinished. It does show the 'API' that we're expecting to
|
||||
use so if you'd like to write your own management server, feel free to look at
|
||||
@@ -104,7 +106,7 @@ of temporary generated scripts, which we can't possibly whitelist and not doing
|
||||
so would cause problems. We're happy to revisit this (or at least make it an
|
||||
option) if it would be useful to others.
|
||||
|
||||
* Documentation: There currently isn't any.
|
||||
* Documentation: This is currently limited.
|
||||
|
||||
* Tests: There aren't enough of them.
|
||||
|
||||
@@ -119,6 +121,9 @@ A tool like Santa doesn't really lend itself to screenshots, so here's a video i
|
||||
|
||||
Building
|
||||
========
|
||||
Firstly, make sure you're using Xcode 7.3.1 as currently we do not support
|
||||
building with Xcode 8.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/google/santa
|
||||
cd santa
|
||||
@@ -136,7 +141,7 @@ and for security-reasons parts of Santa will not operate properly if not signed.
|
||||
|
||||
Kext Signing
|
||||
============
|
||||
Kernel extensions on OS X 10.9 and later must be signed using an Apple-provided
|
||||
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
|
||||
Developer ID certificate with a kernel extension flag. Without it, the only way
|
||||
to load an extension is to enable kext-dev-mode or disable SIP, depending on the
|
||||
OS version.
|
||||
|
||||
21
Rakefile
21
Rakefile
@@ -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,7 @@ desc "Clean"
|
||||
task :clean => :init do
|
||||
puts "Cleaning"
|
||||
FileUtils.rm_rf(OUTPUT_PATH)
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
xcodebuild("-scheme All clean")
|
||||
end
|
||||
|
||||
# Build
|
||||
@@ -111,24 +110,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
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0D0016A3192BCD3C005E7FCD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9A7F3E1759330500035EB5 /* Foundation.framework */; };
|
||||
0D0016A6192BCD3C005E7FCD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0016A5192BCD3C005E7FCD /* main.m */; };
|
||||
0D0016A6192BCD3C005E7FCD /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0D0016A5192BCD3C005E7FCD /* main.mm */; };
|
||||
0D0016AE192BCD8C005E7FCD /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D3AFBF718FB4C870087BCEE /* IOKit.framework */; };
|
||||
0D0A1EC3191998C900B8450F /* SNTCommandSyncRuleDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.m */; };
|
||||
0D0A1EC6191AB9B000B8450F /* SNTCommandSyncPostflight.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0A1EC5191AB9B000B8450F /* SNTCommandSyncPostflight.m */; };
|
||||
@@ -41,9 +41,11 @@
|
||||
0D202D1B1CDD465400A88F16 /* SNTCommandSyncState.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D416400191974F1006A356A /* SNTCommandSyncState.m */; };
|
||||
0D202D1E1CDD479400A88F16 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D202D1D1CDD479400A88F16 /* libz.tbd */; };
|
||||
0D202D201CE4E90E00A88F16 /* sync_preflight_basic.json in Resources */ = {isa = PBXBuildFile; fileRef = 0D202D1F1CE4E90E00A88F16 /* sync_preflight_basic.json */; };
|
||||
0D202D251CE5071600A88F16 /* SantaCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D202D231CE5071600A88F16 /* SantaCache.h */; };
|
||||
0D20710E1A7C4A86008B0A9A /* SNTStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604A19105433006B445C /* SNTStoredEvent.m */; };
|
||||
0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D42D2B719D2042900955F08 /* SNTConfigurator.m */; };
|
||||
0D2CD4611A81C7B100C9C910 /* dn.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0D2CD4601A81C7B100C9C910 /* dn.plist */; };
|
||||
0D2E1E631CEFA6C30039B2C4 /* SantaCacheTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0D2E1E621CEFA6C30039B2C4 /* SantaCacheTest.mm */; };
|
||||
0D35BD9F18FD71CE00921A21 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9A7F3E1759330500035EB5 /* Foundation.framework */; };
|
||||
0D35BDA218FD71CE00921A21 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDA118FD71CE00921A21 /* main.m */; };
|
||||
0D35BDAC18FD7CFD00921A21 /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; };
|
||||
@@ -86,8 +88,6 @@
|
||||
0D6F12D819EC8822006B218E /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DCD5F771909C659006B445C /* SecurityInterface.framework */; };
|
||||
0D6FDC9618C93A020044685C /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; };
|
||||
0D6FDC9718C93A020044685C /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; };
|
||||
0D7A7AF3174FCF4C00B77646 /* SantaCachedDecision.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0D7A7AF1174FCF4C00B77646 /* SantaCachedDecision.cc */; };
|
||||
0D7A7AF4174FCF4C00B77646 /* SantaCachedDecision.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D7A7AF2174FCF4C00B77646 /* SantaCachedDecision.h */; };
|
||||
0D7D01871774F93A005DBAB4 /* SNTDriverManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */; };
|
||||
0D827E6519DF392E006EC811 /* SNTConfigurator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D42D2B719D2042900955F08 /* SNTConfigurator.m */; };
|
||||
0D827E6719DF3C74006EC811 /* SNTCommandStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827E6619DF3C74006EC811 /* SNTCommandStatus.m */; };
|
||||
@@ -118,8 +118,6 @@
|
||||
0DB77FDB1CD14093004DF060 /* SNTBlockMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DB77FD71CCE824A004DF060 /* SNTBlockMessage.m */; };
|
||||
0DB77FFB1CD7AC5A004DF060 /* SNTCommandSyncConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */; };
|
||||
0DB8ACC1185662DC00FEF9C7 /* SNTApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */; };
|
||||
0DB98BFA1C1B9F3000B65DB1 /* SantaPIDAndPPID.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0DB98BF81C1B9F3000B65DB1 /* SantaPIDAndPPID.cc */; };
|
||||
0DB98BFB1C1B9F3000B65DB1 /* SantaPIDAndPPID.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DB98BF91C1B9F3000B65DB1 /* SantaPIDAndPPID.h */; };
|
||||
0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */; };
|
||||
0DC5D86E191AED220078A5C0 /* SNTRuleTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */; };
|
||||
0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */; };
|
||||
@@ -173,8 +171,24 @@
|
||||
1C299D1C789489996FF9E081 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 87D1CEAEDF1FA6819A855559 /* libPods-Santa.a */; };
|
||||
29C490B1720D4FD576F93519 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17D03B346587131C45A8DA67 /* libPods-LogicTests.a */; };
|
||||
2BA4AE89AA2447E29DA2E85C /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BE53E1EAE84D54E7FCB22FD5 /* libPods-santactl.a */; };
|
||||
34D8F6C53153950A66DBEF69 /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 60A9B66BB0F7404D1F61D518 /* libPods-santad.a */; };
|
||||
4092327A1A51B66400A04527 /* SNTCommandRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 409232791A51B65D00A04527 /* SNTCommandRule.m */; };
|
||||
580046F725A5D0874B970A17 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2B9044B79DD2E4DEC5D3B7A /* libPods-Santa.a */; };
|
||||
79C1556E6EAC94038762EF36 /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 556108C12FC29E329D82D4CB /* libPods-santactl.a */; };
|
||||
A60673DE57680AC450A3B0B2 /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BE438428F17C09C6A9D0802 /* libPods-santad.a */; };
|
||||
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */; };
|
||||
C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; };
|
||||
C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */; };
|
||||
C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; };
|
||||
C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
|
||||
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
|
||||
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */; };
|
||||
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
|
||||
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
|
||||
C7FB56F61DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
|
||||
C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
|
||||
C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; };
|
||||
EFD8E30D32F6128B9E833D64 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 873978BCE4B0DBD2A89C99D1 /* libPods-LogicTests.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -252,7 +266,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0D0016A2192BCD3C005E7FCD /* KernelTests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = KernelTests; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0D0016A5192BCD3C005E7FCD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
0D0016A5192BCD3C005E7FCD /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
|
||||
0D0A1EC1191998C900B8450F /* SNTCommandSyncRuleDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncRuleDownload.h; sourceTree = "<group>"; };
|
||||
0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncRuleDownload.m; sourceTree = "<group>"; };
|
||||
0D0A1EC4191AB9B000B8450F /* SNTCommandSyncPostflight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncPostflight.h; sourceTree = "<group>"; };
|
||||
@@ -265,6 +279,7 @@
|
||||
0D202D181CDD2EE500A88F16 /* SNTCommandSyncTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncTest.m; sourceTree = "<group>"; };
|
||||
0D202D1D1CDD479400A88F16 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
|
||||
0D202D1F1CE4E90E00A88F16 /* sync_preflight_basic.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sync_preflight_basic.json; sourceTree = "<group>"; };
|
||||
0D202D231CE5071600A88F16 /* SantaCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaCache.h; sourceTree = "<group>"; };
|
||||
0D260DAC18B68E12002A0B55 /* LogicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LogicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0D260DB118B68E12002A0B55 /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
|
||||
0D260DB718B68E12002A0B55 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
|
||||
@@ -272,6 +287,7 @@
|
||||
0D28E5E31926AFE400280F87 /* SNTKernelCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTKernelCommon.h; sourceTree = "<group>"; };
|
||||
0D28E5E41926B55600280F87 /* santactl-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "santactl-Info.plist"; sourceTree = "<group>"; };
|
||||
0D2CD4601A81C7B100C9C910 /* dn.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = dn.plist; sourceTree = "<group>"; };
|
||||
0D2E1E621CEFA6C30039B2C4 /* SantaCacheTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SantaCacheTest.mm; sourceTree = "<group>"; };
|
||||
0D35BD9E18FD71CE00921A21 /* santactl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = santactl; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0D35BDA118FD71CE00921A21 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
0D35BDA418FD71CE00921A21 /* santactl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "santactl-Prefix.pch"; sourceTree = "<group>"; };
|
||||
@@ -321,8 +337,6 @@
|
||||
0D668E8018D1121700E29A8B /* SNTMessageWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTMessageWindow.m; sourceTree = "<group>"; };
|
||||
0D6FDC9418C93A020044685C /* SNTXPCConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCConnection.h; sourceTree = "<group>"; };
|
||||
0D6FDC9518C93A020044685C /* SNTXPCConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCConnection.m; sourceTree = "<group>"; };
|
||||
0D7A7AF1174FCF4C00B77646 /* SantaCachedDecision.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SantaCachedDecision.cc; sourceTree = "<group>"; };
|
||||
0D7A7AF2174FCF4C00B77646 /* SantaCachedDecision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaCachedDecision.h; sourceTree = "<group>"; };
|
||||
0D7D01851774F93A005DBAB4 /* SNTDriverManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDriverManager.h; sourceTree = "<group>"; };
|
||||
0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = SNTDriverManager.m; sourceTree = "<group>"; };
|
||||
0D827E6619DF3C74006EC811 /* SNTCommandStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandStatus.m; sourceTree = "<group>"; };
|
||||
@@ -351,8 +365,6 @@
|
||||
0DB8ACBF185662DC00FEF9C7 /* SNTApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTApplication.h; sourceTree = "<group>"; };
|
||||
0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SNTApplication.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
0DB8ACE41858D73000FEF9C7 /* santad-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "santad-Info.plist"; sourceTree = "<group>"; };
|
||||
0DB98BF81C1B9F3000B65DB1 /* SantaPIDAndPPID.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SantaPIDAndPPID.cc; sourceTree = "<group>"; };
|
||||
0DB98BF91C1B9F3000B65DB1 /* SantaPIDAndPPID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaPIDAndPPID.h; sourceTree = "<group>"; };
|
||||
0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTRuleTable.m; sourceTree = "<group>"; };
|
||||
0DC5D86F192160180078A5C0 /* SNTCommandSyncLogUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncLogUpload.h; sourceTree = "<group>"; };
|
||||
0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncLogUpload.m; sourceTree = "<group>"; };
|
||||
@@ -399,13 +411,27 @@
|
||||
409232791A51B65D00A04527 /* SNTCommandRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandRule.m; sourceTree = "<group>"; };
|
||||
4D9D2DDDCD92DBB948D38B11 /* Pods-Santa.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.release.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.release.xcconfig"; sourceTree = "<group>"; };
|
||||
4E43227BA5B261FF33141AFC /* Pods-LogicTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LogicTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
556108C12FC29E329D82D4CB /* libPods-santactl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santactl.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
60A9B66BB0F7404D1F61D518 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
691189054F4E484D030CF831 /* Pods-santactl.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santactl.debug.xcconfig"; path = "Pods/Target Support Files/Pods-santactl/Pods-santactl.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7C9CC3CDF2609E78E6A9C601 /* Pods-santad.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santad.release.xcconfig"; path = "Pods/Target Support Files/Pods-santad/Pods-santad.release.xcconfig"; sourceTree = "<group>"; };
|
||||
821428941753678DB772D761 /* Pods-LogicTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LogicTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
873978BCE4B0DBD2A89C99D1 /* libPods-LogicTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LogicTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
87D1CEAEDF1FA6819A855559 /* libPods-Santa.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Santa.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
95B378553DCD86290341B8E4 /* Pods-santad.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santad.debug.xcconfig"; path = "Pods/Target Support Files/Pods-santad/Pods-santad.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9BE438428F17C09C6A9D0802 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B2B9044B79DD2E4DEC5D3B7A /* libPods-Santa.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Santa.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BE53E1EAE84D54E7FCB22FD5 /* libPods-santactl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santactl.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFileInfoTest.m; sourceTree = "<group>"; };
|
||||
C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandCheckCache.m; sourceTree = "<group>"; };
|
||||
C776A1051DEE160500A56616 /* SNTCommandSyncManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncManager.h; sourceTree = "<group>"; };
|
||||
C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncManager.m; sourceTree = "<group>"; };
|
||||
C795ED8E1D80A5BE007CFF42 /* SNTPolicyProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTPolicyProcessor.h; sourceTree = "<group>"; };
|
||||
C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTPolicyProcessor.m; sourceTree = "<group>"; };
|
||||
C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCSyncdInterface.h; sourceTree = "<group>"; };
|
||||
C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCSyncdInterface.m; sourceTree = "<group>"; };
|
||||
C7FB56FE1DBFC213004E14EF /* SNTSyncdQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTSyncdQueue.h; sourceTree = "<group>"; };
|
||||
C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTSyncdQueue.m; sourceTree = "<group>"; };
|
||||
D227889DF327E7D3532FE00B /* Pods-Santa.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -427,6 +453,7 @@
|
||||
0D3AFBF618FB4C7E0087BCEE /* Cocoa.framework in Frameworks */,
|
||||
0D3AFBF818FB4C870087BCEE /* IOKit.framework in Frameworks */,
|
||||
29C490B1720D4FD576F93519 /* libPods-LogicTests.a in Frameworks */,
|
||||
EFD8E30D32F6128B9E833D64 /* libPods-LogicTests.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -439,6 +466,7 @@
|
||||
0D35BDBD18FDA23600921A21 /* IOKit.framework in Frameworks */,
|
||||
0D35BD9F18FD71CE00921A21 /* Foundation.framework in Frameworks */,
|
||||
2BA4AE89AA2447E29DA2E85C /* libPods-santactl.a in Frameworks */,
|
||||
79C1556E6EAC94038762EF36 /* libPods-santactl.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -451,6 +479,7 @@
|
||||
0D8C200C180F359A00CE2BF8 /* Security.framework in Frameworks */,
|
||||
0D385DB8180DE4A900418BC6 /* Cocoa.framework in Frameworks */,
|
||||
1C299D1C789489996FF9E081 /* libPods-Santa.a in Frameworks */,
|
||||
580046F725A5D0874B970A17 /* libPods-Santa.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -462,6 +491,7 @@
|
||||
0D4A5007176A4602004F63BF /* Security.framework in Frameworks */,
|
||||
0D9A7F3F1759330500035EB5 /* Foundation.framework in Frameworks */,
|
||||
A60673DE57680AC450A3B0B2 /* libPods-santad.a in Frameworks */,
|
||||
34D8F6C53153950A66DBEF69 /* libPods-santad.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -478,7 +508,7 @@
|
||||
0D0016A4192BCD3C005E7FCD /* KernelTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D0016A5192BCD3C005E7FCD /* main.m */,
|
||||
0D0016A5192BCD3C005E7FCD /* main.mm */,
|
||||
);
|
||||
path = KernelTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -486,7 +516,10 @@
|
||||
0D260DAF18B68E12002A0B55 /* LogicTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D91BCB6174E8A7E00131A7D /* Frameworks */,
|
||||
0D260DB018B68E12002A0B55 /* Resources */,
|
||||
0D2E1E621CEFA6C30039B2C4 /* SantaCacheTest.mm */,
|
||||
C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */,
|
||||
0D202D181CDD2EE500A88F16 /* SNTCommandSyncTest.m */,
|
||||
0D41DAD31A7C28C800A890FE /* SNTEventTableTest.m */,
|
||||
0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */,
|
||||
@@ -549,6 +582,8 @@
|
||||
0D41640419197AD7006A356A /* SNTCommandSyncEventUpload.m */,
|
||||
0DC5D86F192160180078A5C0 /* SNTCommandSyncLogUpload.h */,
|
||||
0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */,
|
||||
C776A1051DEE160500A56616 /* SNTCommandSyncManager.h */,
|
||||
C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */,
|
||||
0D0A1EC4191AB9B000B8450F /* SNTCommandSyncPostflight.h */,
|
||||
0D0A1EC5191AB9B000B8450F /* SNTCommandSyncPostflight.m */,
|
||||
0DCD605A19117A90006B445C /* SNTCommandSyncPreflight.h */,
|
||||
@@ -619,9 +654,9 @@
|
||||
children = (
|
||||
0DB77FDC1CD262F5004DF060 /* Source */,
|
||||
0D789F9F1940F26D0036F7C4 /* Tests */,
|
||||
0D91BCB6174E8A7E00131A7D /* Frameworks */,
|
||||
0D91BCB5174E8A7E00131A7D /* Products */,
|
||||
57575205DAC12A357F9EF899 /* Pods */,
|
||||
9A8F78FCF1FF5934C80FB9B4 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -656,21 +691,19 @@
|
||||
9BE438428F17C09C6A9D0802 /* libPods-santad.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
path = ../..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0D91BCB9174E8A7E00131A7D /* santa-driver */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D7A7AF2174FCF4C00B77646 /* SantaCachedDecision.h */,
|
||||
0D7A7AF1174FCF4C00B77646 /* SantaCachedDecision.cc */,
|
||||
0D202D231CE5071600A88F16 /* SantaCache.h */,
|
||||
0D4644C4182AF81700098690 /* SantaDecisionManager.h */,
|
||||
0D4644C3182AF81700098690 /* SantaDecisionManager.cc */,
|
||||
0D9A7F321759144800035EB5 /* SantaDriver.h */,
|
||||
0D9A7F311759144800035EB5 /* SantaDriver.cc */,
|
||||
0D9A7F361759148E00035EB5 /* SantaDriverClient.h */,
|
||||
0D9A7F351759148E00035EB5 /* SantaDriverClient.cc */,
|
||||
0DB98BF91C1B9F3000B65DB1 /* SantaPIDAndPPID.h */,
|
||||
0DB98BF81C1B9F3000B65DB1 /* SantaPIDAndPPID.cc */,
|
||||
0DA36C1F199EA46600A129D6 /* Resources */,
|
||||
);
|
||||
path = "santa-driver";
|
||||
@@ -706,6 +739,8 @@
|
||||
0DCD605419115D17006B445C /* SNTXPCControlInterface.m */,
|
||||
0DC8C9E3180CC3BC00FCFB29 /* SNTXPCNotifierInterface.h */,
|
||||
0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */,
|
||||
C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */,
|
||||
C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */,
|
||||
);
|
||||
path = common;
|
||||
sourceTree = "<group>";
|
||||
@@ -723,6 +758,8 @@
|
||||
0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */,
|
||||
0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */,
|
||||
0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */,
|
||||
C795ED8E1D80A5BE007CFF42 /* SNTPolicyProcessor.h */,
|
||||
C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */,
|
||||
0D7D01851774F93A005DBAB4 /* SNTDriverManager.h */,
|
||||
0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */,
|
||||
0D536ED91B94E9230039A26D /* SNTEventLog.h */,
|
||||
@@ -731,6 +768,8 @@
|
||||
0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */,
|
||||
0DE5B5491C926E3300C00603 /* SNTNotificationQueue.h */,
|
||||
0DE5B54A1C926E3300C00603 /* SNTNotificationQueue.m */,
|
||||
C7FB56FE1DBFC213004E14EF /* SNTSyncdQueue.h */,
|
||||
C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */,
|
||||
0D3AF83118F87CEF0087BCEE /* Resources */,
|
||||
);
|
||||
path = santad;
|
||||
@@ -760,6 +799,7 @@
|
||||
0DAF01141C1B794B00F5B6C3 /* Commands */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */,
|
||||
0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */,
|
||||
0DE4C8A518FF3B1700466D04 /* SNTCommandFlushCache.m */,
|
||||
409232791A51B65D00A04527 /* SNTCommandRule.m */,
|
||||
@@ -797,6 +837,17 @@
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A8F78FCF1FF5934C80FB9B4 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
873978BCE4B0DBD2A89C99D1 /* libPods-LogicTests.a */,
|
||||
B2B9044B79DD2E4DEC5D3B7A /* libPods-Santa.a */,
|
||||
556108C12FC29E329D82D4CB /* libPods-santactl.a */,
|
||||
60A9B66BB0F7404D1F61D518 /* libPods-santad.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -804,8 +855,7 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0D7A7AF4174FCF4C00B77646 /* SantaCachedDecision.h in Headers */,
|
||||
0DB98BFB1C1B9F3000B65DB1 /* SantaPIDAndPPID.h in Headers */,
|
||||
0D202D251CE5071600A88F16 /* SantaCache.h in Headers */,
|
||||
0D4644C6182AF81700098690 /* SantaDecisionManager.h in Headers */,
|
||||
0D9A7F341759144800035EB5 /* SantaDriver.h in Headers */,
|
||||
0D9A7F381759148E00035EB5 /* SantaDriverClient.h in Headers */,
|
||||
@@ -835,13 +885,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0D260DBC18B68E12002A0B55 /* Build configuration list for PBXNativeTarget "LogicTests" */;
|
||||
buildPhases = (
|
||||
C88551472F6983D0B00880F8 /* 📦 Check Pods Manifest.lock */,
|
||||
376E230296F6EA7A4DA8BBF0 /* [CP] Check Pods Manifest.lock */,
|
||||
0D673DAD18FC9017009C5B06 /* Delete existing coverage files */,
|
||||
0D260DA818B68E12002A0B55 /* Sources */,
|
||||
0D260DA918B68E12002A0B55 /* Frameworks */,
|
||||
0D260DAA18B68E12002A0B55 /* Resources */,
|
||||
1D12555F0F4EF323B11E40F9 /* 📦 Embed Pods Frameworks */,
|
||||
0C5C7A6AB763BCE7F760FAFF /* 📦 Copy Pods Resources */,
|
||||
1D12555F0F4EF323B11E40F9 /* [CP] Embed Pods Frameworks */,
|
||||
0C5C7A6AB763BCE7F760FAFF /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -856,11 +906,11 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0D35BDA918FD71CE00921A21 /* Build configuration list for PBXNativeTarget "santactl" */;
|
||||
buildPhases = (
|
||||
EE6ABBFEC2708CE57EB1780A /* 📦 Check Pods Manifest.lock */,
|
||||
5F1504EA0D172767F2BCAAEB /* [CP] Check Pods Manifest.lock */,
|
||||
0DD98E671A5DD02000A754C6 /* Update Version Info */,
|
||||
0D35BD9A18FD71CE00921A21 /* Sources */,
|
||||
0D35BD9B18FD71CE00921A21 /* Frameworks */,
|
||||
E50DD7319E04737B040B69EC /* 📦 Copy Pods Resources */,
|
||||
E50DD7319E04737B040B69EC /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -875,13 +925,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0D385DE3180DE4A900418BC6 /* Build configuration list for PBXNativeTarget "Santa" */;
|
||||
buildPhases = (
|
||||
B34356D1FD4C8C6EF75F2FFE /* 📦 Check Pods Manifest.lock */,
|
||||
373591F801D7B22635DAD7A0 /* [CP] Check Pods Manifest.lock */,
|
||||
0DD98E681A5DD03E00A754C6 /* Update Version Info */,
|
||||
0D385DB2180DE4A900418BC6 /* Sources */,
|
||||
0D385DB3180DE4A900418BC6 /* Frameworks */,
|
||||
0D385DB4180DE4A900418BC6 /* Resources */,
|
||||
31CD7EDCDEBD95322ED67F63 /* 📦 Embed Pods Frameworks */,
|
||||
B3EB60284D47F89140F5A033 /* 📦 Copy Pods Resources */,
|
||||
31CD7EDCDEBD95322ED67F63 /* [CP] Embed Pods Frameworks */,
|
||||
B3EB60284D47F89140F5A033 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -918,11 +968,11 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0D9A7F471759330500035EB5 /* Build configuration list for PBXNativeTarget "santad" */;
|
||||
buildPhases = (
|
||||
A3D478EF1D48EA118AF176E9 /* 📦 Check Pods Manifest.lock */,
|
||||
A3D478EF1D48EA118AF176E9 /* [CP] Check Pods Manifest.lock */,
|
||||
0DD98E661A5DCED300A754C6 /* Update Version Info */,
|
||||
0D9A7F391759330400035EB5 /* Sources */,
|
||||
0D9A7F3A1759330400035EB5 /* Frameworks */,
|
||||
435B0E246EE25ACC763D684C /* 📦 Copy Pods Resources */,
|
||||
435B0E246EE25ACC763D684C /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -939,7 +989,7 @@
|
||||
0D91BCA8174E8A6500131A7D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0720;
|
||||
LastUpgradeCheck = 0730;
|
||||
TargetAttributes = {
|
||||
0D260DAB18B68E12002A0B55 = {
|
||||
TestTargetID = 0D385DB5180DE4A900418BC6;
|
||||
@@ -1000,14 +1050,14 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
0C5C7A6AB763BCE7F760FAFF /* 📦 Copy Pods Resources */ = {
|
||||
0C5C7A6AB763BCE7F760FAFF /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1108,14 +1158,14 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "GIT_TAG=$(git describe --abbrev=0 --tags)\nsed -i '' \"s/TO.BE.FILLED/${GIT_TAG}/\" ${DERIVED_FILE_DIR}/santa-driver_info.c";
|
||||
};
|
||||
1D12555F0F4EF323B11E40F9 /* 📦 Embed Pods Frameworks */ = {
|
||||
1D12555F0F4EF323B11E40F9 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Embed Pods Frameworks";
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1123,14 +1173,14 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
31CD7EDCDEBD95322ED67F63 /* 📦 Embed Pods Frameworks */ = {
|
||||
31CD7EDCDEBD95322ED67F63 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Embed Pods Frameworks";
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1138,14 +1188,44 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Santa/Pods-Santa-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
435B0E246EE25ACC763D684C /* 📦 Copy Pods Resources */ = {
|
||||
373591F801D7B22635DAD7A0 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
376E230296F6EA7A4DA8BBF0 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
435B0E246EE25ACC763D684C /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1153,14 +1233,14 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santad/Pods-santad-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A3D478EF1D48EA118AF176E9 /* 📦 Check Pods Manifest.lock */ = {
|
||||
5F1504EA0D172767F2BCAAEB /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Check Pods Manifest.lock";
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1168,14 +1248,14 @@
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B34356D1FD4C8C6EF75F2FFE /* 📦 Check Pods Manifest.lock */ = {
|
||||
A3D478EF1D48EA118AF176E9 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Check Pods Manifest.lock";
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1183,14 +1263,14 @@
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B3EB60284D47F89140F5A033 /* 📦 Copy Pods Resources */ = {
|
||||
B3EB60284D47F89140F5A033 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1198,29 +1278,14 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Santa/Pods-Santa-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
C88551472F6983D0B00880F8 /* 📦 Check Pods Manifest.lock */ = {
|
||||
E50DD7319E04737B040B69EC /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E50DD7319E04737B040B69EC /* 📦 Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1228,21 +1293,6 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santactl/Pods-santactl-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EE6ABBFEC2708CE57EB1780A /* 📦 Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -1250,7 +1300,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0D0016A6192BCD3C005E7FCD /* main.m in Sources */,
|
||||
0D0016A6192BCD3C005E7FCD /* main.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1258,9 +1308,12 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */,
|
||||
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */,
|
||||
0D88680C1AC48A1400B86659 /* SNTSystemInfo.m in Sources */,
|
||||
0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */,
|
||||
0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */,
|
||||
C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */,
|
||||
0DB77FDB1CD14093004DF060 /* SNTBlockMessage.m in Sources */,
|
||||
0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */,
|
||||
0D202D191CDD2EE500A88F16 /* SNTCommandSyncTest.m in Sources */,
|
||||
@@ -1277,6 +1330,8 @@
|
||||
0D202D1A1CDD464B00A88F16 /* SNTCommandSyncPreflight.m in Sources */,
|
||||
0D10BE891A0AAF6700C0C944 /* SNTDropRootPrivs.m in Sources */,
|
||||
0DEFB7C61ACDE5F600B92AAE /* SNTFileWatcher.m in Sources */,
|
||||
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */,
|
||||
C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */,
|
||||
0DEFB7C81ACF0BFE00B92AAE /* SNTFileWatcherTest.m in Sources */,
|
||||
0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */,
|
||||
0DE5B54C1C92722300C00603 /* SNTNotificationQueue.m in Sources */,
|
||||
@@ -1285,6 +1340,7 @@
|
||||
0D3AFBE718FB32CB0087BCEE /* SNTXPCConnectionTest.m in Sources */,
|
||||
0D9184B91CD2F32D0004E859 /* SNTCommandSyncStage.m in Sources */,
|
||||
0DCD605719115E54006B445C /* SNTDaemonControlController.m in Sources */,
|
||||
0D2E1E631CEFA6C30039B2C4 /* SantaCacheTest.mm in Sources */,
|
||||
0D41DAD41A7C28C800A890FE /* SNTEventTableTest.m in Sources */,
|
||||
0D3AFBEE18FB4C6C0087BCEE /* SNTApplication.m in Sources */,
|
||||
0DD0D48F194F78F8005F27EB /* SNTFileInfoTest.m in Sources */,
|
||||
@@ -1294,6 +1350,7 @@
|
||||
0DCD605919115E5A006B445C /* SNTXPCNotifierInterface.m in Sources */,
|
||||
0DE50F691912B0CD007B2B0C /* SNTRule.m in Sources */,
|
||||
0D202D1B1CDD465400A88F16 /* SNTCommandSyncState.m in Sources */,
|
||||
C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1308,14 +1365,17 @@
|
||||
0DCD605619115D17006B445C /* SNTXPCControlInterface.m in Sources */,
|
||||
0DE50F6C19130358007B2B0C /* SNTStoredEvent.m in Sources */,
|
||||
0D9184B81CD2F32D0004E859 /* SNTCommandSyncStage.m in Sources */,
|
||||
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */,
|
||||
0D35BDC418FDA5D100921A21 /* SNTXPCConnection.m in Sources */,
|
||||
0DCD605C19117A90006B445C /* SNTCommandSyncPreflight.m in Sources */,
|
||||
0D41640519197AD7006A356A /* SNTCommandSyncEventUpload.m in Sources */,
|
||||
0D42D2B919D2042900955F08 /* SNTConfigurator.m in Sources */,
|
||||
0DF395641AB76A7900CBC520 /* NSData+Zlib.m in Sources */,
|
||||
C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */,
|
||||
0D10BE871A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */,
|
||||
0DE4C8A618FF3B1700466D04 /* SNTCommandFlushCache.m in Sources */,
|
||||
4092327A1A51B66400A04527 /* SNTCommandRule.m in Sources */,
|
||||
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */,
|
||||
0D416401191974F1006A356A /* SNTCommandSyncState.m in Sources */,
|
||||
0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */,
|
||||
0DB77FDA1CD14092004DF060 /* SNTBlockMessage.m in Sources */,
|
||||
@@ -1359,8 +1419,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0D7A7AF3174FCF4C00B77646 /* SantaCachedDecision.cc in Sources */,
|
||||
0DB98BFA1C1B9F3000B65DB1 /* SantaPIDAndPPID.cc in Sources */,
|
||||
0D9A7F331759144800035EB5 /* SantaDriver.cc in Sources */,
|
||||
0D9A7F371759148E00035EB5 /* SantaDriverClient.cc in Sources */,
|
||||
0D4644C5182AF81700098690 /* SantaDecisionManager.cc in Sources */,
|
||||
@@ -1376,10 +1434,12 @@
|
||||
0D10BE861A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */,
|
||||
0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */,
|
||||
0DCD604B19105433006B445C /* SNTStoredEvent.m in Sources */,
|
||||
C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */,
|
||||
0DB8ACC1185662DC00FEF9C7 /* SNTApplication.m in Sources */,
|
||||
0D9A7F421759330500035EB5 /* main.m in Sources */,
|
||||
0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */,
|
||||
0DE71A751B95F7F900518526 /* SNTCachedDecision.m in Sources */,
|
||||
C7FB56F61DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */,
|
||||
0DCD6042190ACCB8006B445C /* SNTFileInfo.m in Sources */,
|
||||
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
|
||||
0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */,
|
||||
@@ -1392,6 +1452,7 @@
|
||||
0D37C10F18F6029A0069BC61 /* SNTDatabaseTable.m in Sources */,
|
||||
0D42D2B819D2042900955F08 /* SNTConfigurator.m in Sources */,
|
||||
0DCD605519115D17006B445C /* SNTXPCControlInterface.m in Sources */,
|
||||
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */,
|
||||
0D536EDB1B94E9230039A26D /* SNTEventLog.m in Sources */,
|
||||
0DCD604F19115A06006B445C /* SNTXPCNotifierInterface.m in Sources */,
|
||||
0DE5B54B1C926E3300C00603 /* SNTNotificationQueue.m in Sources */,
|
||||
@@ -1460,7 +1521,6 @@
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -1534,7 +1594,6 @@
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_GENERATE_TEST_COVERAGE_FILES = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Tests/LogicTests/Resources/Tests-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -1624,7 +1683,6 @@
|
||||
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Source/santactl/Resources/santactl-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -1701,7 +1759,6 @@
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Source/SantaGUI/Resources/Santa-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -1769,6 +1826,8 @@
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = c99;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
LLVM_LTO = NO;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PROVISIONING_PROFILE = "";
|
||||
@@ -1793,6 +1852,8 @@
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
GCC_C_LANGUAGE_STANDARD = c99;
|
||||
GCC_OPTIMIZATION_LEVEL = fast;
|
||||
LLVM_LTO = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
PROVISIONING_PROFILE = "";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
@@ -1820,7 +1881,6 @@
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
@@ -1909,7 +1969,6 @@
|
||||
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Source/santad/Resources/santad-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -27,7 +27,8 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
codeCoverageEnabled = "YES"
|
||||
enableAddressSanitizer = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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="15G31" 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">
|
||||
@@ -321,7 +321,7 @@ DQ
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<buttonCell key="cell" type="roundTextured" title="Ignore" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
|
||||
handler:^(unsigned long data) {
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
if (! (data & DISPATCH_VNODE_ATTRIB)) [[SNTConfigurator configurator] reloadConfigData];
|
||||
}];
|
||||
|
||||
self.notificationManager = [[SNTNotificationManager alloc] init];
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
if (![key isEqualToString:@"event"]) {
|
||||
return [NSSet setWithObject:@"event"];
|
||||
} else {
|
||||
return nil;
|
||||
return [NSSet set];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
case SNTClientModeLockdown:
|
||||
un.informativeText = @"Switching into Lockdown mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
|
||||
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
if (customMsg.length) un.informativeText = customMsg;
|
||||
break;
|
||||
default:
|
||||
@@ -135,4 +135,12 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
});
|
||||
}
|
||||
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
|
||||
NSUserNotification *un = [[NSUserNotification alloc] init];
|
||||
un.title = @"Santa";
|
||||
un.hasActionButton = NO;
|
||||
un.informativeText = message ?: @"Requested application can now be run";
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -92,9 +92,16 @@
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr;
|
||||
if (config.eventDetailBundleURL && event.fileBundleID) {
|
||||
NSString *formatStr, *versionStr;
|
||||
if (config.eventDetailBundleURL.length && event.fileBundleID) {
|
||||
formatStr = config.eventDetailBundleURL;
|
||||
versionStr = event.fileBundleVersion;
|
||||
if (!versionStr) versionStr = event.fileBundleVersionString;
|
||||
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
|
||||
withString:event.fileBundleID];
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
|
||||
withString:versionStr];
|
||||
} else {
|
||||
formatStr = config.eventDetailURL;
|
||||
}
|
||||
@@ -113,14 +120,6 @@
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
if (event.fileBundleID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
|
||||
withString:event.fileBundleID];
|
||||
}
|
||||
if (event.fileBundleVersionString) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
|
||||
withString:event.fileBundleVersionString];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
}
|
||||
|
||||
@@ -41,19 +41,25 @@ typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
SNTEventStateUnknown,
|
||||
// Bits 0-15 bits store non-decision types
|
||||
SNTEventStateUnknown = 0,
|
||||
SNTEventStateBundleBinary = 1,
|
||||
|
||||
SNTEventStateAllowUnknown = 1,
|
||||
SNTEventStateAllowBinary = 2,
|
||||
SNTEventStateAllowCertificate = 3,
|
||||
SNTEventStateAllowScope = 4,
|
||||
// Bits 16-23 store deny decision types
|
||||
SNTEventStateBlockUnknown = 1 << 16,
|
||||
SNTEventStateBlockBinary = 1 << 17,
|
||||
SNTEventStateBlockCertificate = 1 << 18,
|
||||
SNTEventStateBlockScope = 1 << 19,
|
||||
|
||||
SNTEventStateBlockUnknown = 5,
|
||||
SNTEventStateBlockBinary = 6,
|
||||
SNTEventStateBlockCertificate = 7,
|
||||
SNTEventStateBlockScope = 8,
|
||||
// Bits 24-31 store allow decision types
|
||||
SNTEventStateAllowUnknown = 1 << 24,
|
||||
SNTEventStateAllowBinary = 1 << 25,
|
||||
SNTEventStateAllowCertificate = 1 << 26,
|
||||
SNTEventStateAllowScope = 1 << 27,
|
||||
|
||||
SNTEventStateBundleBinary = 9,
|
||||
// Block and Allow masks
|
||||
SNTEventStateBlock = 0xFF << 16,
|
||||
SNTEventStateAllow = 0xFF << 24
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
|
||||
@@ -143,9 +143,14 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
@property(readonly, nonatomic) NSString *machineOwner;
|
||||
|
||||
///
|
||||
/// The last date of successful sync.
|
||||
/// The last date of a successful full sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *syncLastSuccess;
|
||||
@property(nonatomic) NSDate *fullSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// The last date of a successful rule sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *ruleSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// If YES a clean sync is required.
|
||||
|
||||
@@ -52,7 +52,8 @@ static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kSyncLastSuccess = @"SyncLastSuccess";
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
@@ -100,7 +101,13 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
NSInteger cm = [self.configData[kClientModeKey] longValue];
|
||||
NSInteger cm = SNTClientModeUnknown;
|
||||
|
||||
id mode = self.configData[kClientModeKey];
|
||||
if ([mode respondsToSelector:@selector(longLongValue)]) {
|
||||
cm = (NSInteger)[mode longLongValue];
|
||||
}
|
||||
|
||||
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
|
||||
return (SNTClientMode)cm;
|
||||
} else {
|
||||
@@ -253,12 +260,22 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return self.configData[kServerAuthRootsFileKey];
|
||||
}
|
||||
|
||||
- (NSDate *)syncLastSuccess {
|
||||
return self.configData[kSyncLastSuccess];
|
||||
- (NSDate *)fullSyncLastSuccess {
|
||||
return self.configData[kFullSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setSyncLastSuccess:(NSDate *)syncLastSuccess {
|
||||
self.configData[kSyncLastSuccess] = syncLastSuccess;
|
||||
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
|
||||
self.configData[kFullSyncLastSuccess] = fullSyncLastSuccess;
|
||||
[self saveConfigToDisk];
|
||||
self.ruleSyncLastSuccess = fullSyncLastSuccess;
|
||||
}
|
||||
|
||||
- (NSDate *)ruleSyncLastSuccess {
|
||||
return self.configData[kRuleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
|
||||
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,11 @@
|
||||
///
|
||||
- (BOOL)isDylib;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a bundle executable (QuickLook/Spotlight plugin, etc.)
|
||||
///
|
||||
- (BOOL)isBundle;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a kernel extension.
|
||||
///
|
||||
|
||||
@@ -76,7 +76,17 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_fileHandle = [NSFileHandle fileHandleForReadingAtPath:_path];
|
||||
int fd = open([_path UTF8String], O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
if (error) {
|
||||
NSString *errStr = [NSString stringWithFormat:@"Unable to open file: %s", strerror(errno)];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:280
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
|
||||
|
||||
struct stat fileStat;
|
||||
fstat(_fileHandle.fileDescriptor, &fileStat);
|
||||
@@ -102,7 +112,9 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
#pragma mark Hashing
|
||||
|
||||
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
|
||||
const int chunkSize = 4096;
|
||||
const int MAX_CHUNK_SIZE = 256 * 1024; // 256 KB
|
||||
const size_t chunkSize = _fileSize > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : _fileSize;
|
||||
char chunk[chunkSize];
|
||||
|
||||
CC_SHA1_CTX c1;
|
||||
CC_SHA256_CTX c256;
|
||||
@@ -110,46 +122,59 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
if (sha1) CC_SHA1_Init(&c1);
|
||||
if (sha256) CC_SHA256_Init(&c256);
|
||||
|
||||
for (uint64_t offset = 0; offset < self.fileSize; offset += chunkSize) {
|
||||
@autoreleasepool {
|
||||
int readSize = 0;
|
||||
if (offset + chunkSize > self.fileSize) {
|
||||
readSize = (int)(self.fileSize - offset);
|
||||
} else {
|
||||
readSize = chunkSize;
|
||||
}
|
||||
int fd = self.fileHandle.fileDescriptor;
|
||||
|
||||
NSData *chunk = [self safeSubdataWithRange:NSMakeRange(offset, readSize)];
|
||||
if (!chunk) {
|
||||
if (sha1) CC_SHA1_Final(NULL, &c1);
|
||||
if (sha256) CC_SHA256_Final(NULL, &c256);
|
||||
return;
|
||||
}
|
||||
fcntl(fd, F_RDAHEAD, 1);
|
||||
struct radvisory radv;
|
||||
radv.ra_offset = 0;
|
||||
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
|
||||
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
|
||||
fcntl(fd, F_RDADVISE, &radv);
|
||||
ssize_t bytesRead;
|
||||
|
||||
if (sha1) CC_SHA1_Update(&c1, chunk.bytes, readSize);
|
||||
if (sha256) CC_SHA256_Update(&c256, chunk.bytes, readSize);
|
||||
for (uint64_t offset = 0; offset < _fileSize;) {
|
||||
bytesRead = pread(fd, chunk, chunkSize, offset);
|
||||
if (bytesRead > 0) {
|
||||
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
|
||||
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
|
||||
offset += bytesRead;
|
||||
} else if (bytesRead == -1 && errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We turn off Read Ahead that we turned on
|
||||
fcntl(fd, F_RDAHEAD, 0);
|
||||
if (sha1) {
|
||||
unsigned char dgst[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1_Final(dgst, &c1);
|
||||
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; ++i) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)dgst[i]];
|
||||
}
|
||||
*sha1 = [buf copy];
|
||||
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1_Final(digest, &c1);
|
||||
NSString *const SHA1FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
*sha1 = [[NSString alloc]
|
||||
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19]];
|
||||
}
|
||||
if (sha256) {
|
||||
unsigned char dgst[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256_Final(dgst, &c256);
|
||||
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256_Final(digest, &c256);
|
||||
NSString *const SHA256FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)dgst[i]];
|
||||
}
|
||||
*sha256 = [buf copy];
|
||||
*sha256 = [[NSString alloc]
|
||||
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19], digest[20],
|
||||
digest[21], digest[22], digest[23], digest[24],
|
||||
digest[25], digest[26], digest[27], digest[28],
|
||||
digest[29], digest[30], digest[31]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,22 +196,26 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return [self.machHeaders allKeys];
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
- (uint32_t)machFileType {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_EXECUTE) return YES;
|
||||
return NO;
|
||||
if (mach_header) return mach_header->filetype;
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
return [self machFileType] == MH_EXECUTE;
|
||||
}
|
||||
|
||||
- (BOOL)isDylib {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_DYLIB) return YES;
|
||||
return NO;
|
||||
return [self machFileType] == MH_DYLIB;
|
||||
}
|
||||
|
||||
- (BOOL)isBundle {
|
||||
return [self machFileType] == MH_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isKext {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) return YES;
|
||||
return NO;
|
||||
return [self machFileType] == MH_KEXT_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
@@ -199,18 +228,18 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
- (BOOL)isScript {
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 2)] bytes];
|
||||
return (strncmp("#!", magic, 2) == 0);
|
||||
return (magic && memcmp("#!", magic, 2) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)isXARArchive {
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 4)] bytes];
|
||||
return (strncmp("xar!", magic, 4) == 0);
|
||||
return (magic && memcmp("xar!", magic, 4) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)isDMG {
|
||||
NSUInteger last512 = self.fileSize - 512;
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
|
||||
return (magic && strncmp("koly", magic, 4) == 0);
|
||||
return (magic && memcmp("koly", magic, 4) == 0);
|
||||
}
|
||||
|
||||
#pragma mark Page Zero
|
||||
@@ -230,7 +259,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
if (!lcData) return NO;
|
||||
|
||||
// This code assumes the __PAGEZERO is always the first load-command in the file.
|
||||
// Given that the OS X ABI says "the static linker creates a __PAGEZERO segment
|
||||
// Given that the macOS ABI says "the static linker creates a __PAGEZERO segment
|
||||
// as the first segment of an executable file." this should be OK.
|
||||
struct load_command *lc = (struct load_command *)[lcData bytes];
|
||||
if (lc->cmd == LC_SEGMENT) {
|
||||
@@ -273,9 +302,10 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
// Check that the full path is at least 4-levels deep:
|
||||
// e.g: /Calendar.app/Contents/MacOS/Calendar
|
||||
NSArray *pathComponents = [self.path pathComponents];
|
||||
if ([pathComponents count] < 4) return nil;
|
||||
NSUInteger pathComponentsCount = pathComponents.count;
|
||||
if (pathComponentsCount < 4) return nil;
|
||||
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponentsCount - 3)];
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = bndl;
|
||||
}
|
||||
@@ -371,7 +401,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
NSData *fatHeader = [self safeSubdataWithRange:range];
|
||||
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
|
||||
|
||||
if (fatHeader && (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM)) {
|
||||
if (fatHeader && (fh->magic == FAT_CIGAM || fh->magic == FAT_MAGIC)) {
|
||||
int nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
|
||||
range = NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch) * nfat_arch);
|
||||
NSMutableData *fatArchs = [[self safeSubdataWithRange:range] mutableCopy];
|
||||
@@ -445,7 +475,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
if (!cmdData) return nil;
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
|
||||
if (strncmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
@@ -459,7 +489,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
|
||||
if (!sectData) return nil;
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && strncmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
|
||||
if (!plistData) return nil;
|
||||
NSDictionary *plist;
|
||||
@@ -504,7 +534,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
/// is not the one who downloaded the file.
|
||||
///
|
||||
- (NSDictionary *)quarantineData {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir) {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir && NSURLQuarantinePropertiesKey) {
|
||||
self.quarantineDict = (NSDictionary *)[NSNull null];
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path];
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
/// @param filePath the file to watch.
|
||||
/// @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.
|
||||
/// The handler is always be called on the main thread.
|
||||
///
|
||||
- (nonnull instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
handler:(nonnull void (^)(unsigned long))handler;
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
int fd = -1;
|
||||
while ((fd = open([self.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
|
||||
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);
|
||||
@@ -62,41 +62,30 @@
|
||||
dispatch_source_set_event_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
unsigned long data = dispatch_source_get_data(self.source);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.handler(data);
|
||||
});
|
||||
self.handler(data);
|
||||
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
|
||||
[self stopWatchingFile];
|
||||
[self startWatchingFile];
|
||||
}
|
||||
sleep(2);
|
||||
});
|
||||
|
||||
dispatch_source_set_registration_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.handler(0);
|
||||
});
|
||||
self.handler(0);
|
||||
});
|
||||
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
if (fd > 0) close(fd);
|
||||
close(fd);
|
||||
});
|
||||
|
||||
|
||||
dispatch_resume(self.source);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopWatchingFile {
|
||||
if (!self.source) return;
|
||||
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
dispatch_source_set_event_handler_f(self.source, NULL);
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
close(fd);
|
||||
});
|
||||
|
||||
dispatch_source_cancel(self.source);
|
||||
self.source = nil;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
#define USERCLIENT_CLASS "com_google_SantaDriver"
|
||||
#define USERCLIENT_ID "com.google.santa-driver"
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
@@ -35,6 +39,7 @@ enum SantaDriverMethods {
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientCacheCount,
|
||||
kSantaUserClientCheckCache,
|
||||
|
||||
// Any methods supported by the driver should be added above this line to
|
||||
// ensure this remains the count of methods.
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#ifdef KERNEL
|
||||
|
||||
#include <IOKit/IOLib.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#else // DEBUG
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
@interface SNTStoredEvent : NSObject<NSSecureCoding>
|
||||
|
||||
///
|
||||
/// An index for this event, empty unless the event came from the database.
|
||||
/// An index for this event, randomly generated during initialization.
|
||||
///
|
||||
@property NSNumber *idx;
|
||||
|
||||
|
||||
@@ -57,6 +57,14 @@
|
||||
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_idx = @(arc4random());
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@@ -28,6 +32,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
|
||||
@@ -36,13 +41,28 @@
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(NSError *error))reply;
|
||||
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
|
||||
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
///
|
||||
/// Decision ops
|
||||
///
|
||||
|
||||
///
|
||||
/// @param filePath A Path to the file, can be nil.
|
||||
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
|
||||
/// be calculated by this method from the filePath.
|
||||
/// @param signingCertificate A MOLCertificate object, can be nil.
|
||||
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
|
||||
/// returned. Binary rules take precedence over cert rules.
|
||||
///
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate
|
||||
reply:(void (^)(SNTEventState))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
@@ -52,8 +72,8 @@
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply;
|
||||
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
|
||||
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
|
||||
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
|
||||
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
|
||||
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;
|
||||
@@ -63,6 +83,14 @@
|
||||
///
|
||||
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
|
||||
|
||||
///
|
||||
/// Syncd Ops
|
||||
///
|
||||
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
|
||||
- (void)pushNotifications:(void (^)(BOOL))reply;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTXPCControlInterface : NSObject
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
@end
|
||||
|
||||
@interface SNTXPCNotifierInterface : NSObject
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
@@ -12,21 +12,23 @@
|
||||
/// 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
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
#include <libkern/c++/OSObject.h>
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by santactl and utilized by santad
|
||||
@protocol SNTSyncdXPC
|
||||
- (void)postEventToSyncServer:(SNTStoredEvent *)event;
|
||||
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncdInterface : NSObject
|
||||
|
||||
///
|
||||
/// An OSObject wrapper around a PID and PPID.
|
||||
/// Only OSObject subclasses can be inserted into an OSDictionary.
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
class SantaPIDAndPPID : public OSObject {
|
||||
OSDeclareDefaultStructors(SantaPIDAndPPID)
|
||||
+ (NSXPCInterface *)syncdInterface;
|
||||
|
||||
public:
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTAPIDANDPPID_H
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
@@ -12,6 +12,12 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SantaPIDAndPPID.h"
|
||||
#import "SNTXPCSyncdInterface.h"
|
||||
|
||||
OSDefineMetaClassAndStructors(SantaPIDAndPPID, OSObject);
|
||||
@implementation SNTXPCSyncdInterface
|
||||
|
||||
+ (NSXPCInterface *)syncdInterface {
|
||||
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
|
||||
}
|
||||
|
||||
@end
|
||||
271
Source/santa-driver/SantaCache.h
Normal file
271
Source/santa-driver/SantaCache.h
Normal file
@@ -0,0 +1,271 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
#define SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
|
||||
#include <libkern/OSAtomic.h>
|
||||
#include <libkern/OSTypes.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <IOKit/IOLib.h>
|
||||
#else // KERNEL
|
||||
// Support for unit testing.
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#define panic(args...) printf(args); printf("\n"); abort()
|
||||
#define IOMalloc malloc
|
||||
#define IOMallocAligned(sz, alignment) malloc(sz);
|
||||
#define IOFree(addr, sz) free(addr)
|
||||
#define IOFreeAligned(addr, sz) free(addr)
|
||||
#define OSTestAndSet OSAtomicTestAndSet
|
||||
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
|
||||
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
|
||||
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
|
||||
#endif // KERNEL
|
||||
|
||||
/**
|
||||
A somewhat simple, concurrent linked-list hash table intended for use in IOKit kernel extensions.
|
||||
Maps 64-bit unsigned integer keys to values.
|
||||
|
||||
Enforces a maximum size by clearing all entries if a new value
|
||||
is added that would go over the maximum size declared at creation.
|
||||
|
||||
The number of buckets is calculated as `maximum_size` / `per_bucket`
|
||||
rounded up to the next power of 2. Locking is done per-bucket.
|
||||
*/
|
||||
template<class T> class SantaCache {
|
||||
public:
|
||||
/**
|
||||
Initialize a newly created cache.
|
||||
|
||||
@param maximum_size The maximum number of entries in this cache. Once this
|
||||
number is reached all the entries will be purged.
|
||||
@param per_bucket The target number of entries in each bucket when cache is full.
|
||||
A higher number will result in better performance but higher memory usage.
|
||||
Cannot be higher than 64 to try and ensure buckets don't overflow.
|
||||
*/
|
||||
SantaCache(uint64_t maximum_size = 10000, uint8_t per_bucket = 5) {
|
||||
if (unlikely(per_bucket < 1)) per_bucket = 1;
|
||||
if (unlikely(per_bucket > 64)) per_bucket = 64;
|
||||
max_size_ = maximum_size;
|
||||
bucket_count_ = 1 << (32 - __builtin_clz(
|
||||
((uint32_t)max_size_ / per_bucket) - 1));
|
||||
buckets_ = (struct bucket *)IOMalloc(bucket_count_ * sizeof(struct bucket));
|
||||
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
/**
|
||||
Clear and free memory
|
||||
*/
|
||||
~SantaCache() {
|
||||
clear();
|
||||
IOFree(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
/**
|
||||
Get an element from the cache. Returns zero_ if item doesn't exist.
|
||||
*/
|
||||
T get(uint64_t key) {
|
||||
struct bucket *bucket = &buckets_[hash(key)];
|
||||
lock(bucket);
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
if (entry->key == key) {
|
||||
T val = entry->value;
|
||||
unlock(bucket);
|
||||
return val;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
unlock(bucket);
|
||||
return zero_;
|
||||
}
|
||||
|
||||
/**
|
||||
Set an element in the cache.
|
||||
|
||||
@note If the cache is full when this is called, this will empty the cache before
|
||||
inserting the new value.
|
||||
|
||||
@return if an existing value was replaced, the previous value, otherwise zero_
|
||||
*/
|
||||
T set(uint64_t key, T value) {
|
||||
struct bucket *bucket = &buckets_[hash(key)];
|
||||
lock(bucket);
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
struct entry *previous_entry = nullptr;
|
||||
while (entry != nullptr) {
|
||||
if (entry->key == key) {
|
||||
T existing_value = entry->value;
|
||||
entry->value = value;
|
||||
|
||||
if (value == zero_) {
|
||||
if (previous_entry != nullptr) {
|
||||
previous_entry->next = entry->next;
|
||||
} else {
|
||||
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
|
||||
}
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
OSDecrementAtomic(&count_);
|
||||
}
|
||||
|
||||
unlock(bucket);
|
||||
return existing_value;
|
||||
}
|
||||
previous_entry = entry;
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
// If value is zero_, we're clearing but there's nothing to clear
|
||||
// so we don't need to do anything else.
|
||||
if (value == zero_) {
|
||||
unlock(bucket);
|
||||
return zero_;
|
||||
}
|
||||
|
||||
// Check that adding this new item won't take the cache over its maximum size.
|
||||
if (count_ + 1 > max_size_) {
|
||||
unlock(bucket);
|
||||
lock(&clear_bucket_);
|
||||
// Check again in case clear has already run while waiting for lock
|
||||
if (count_ + 1 > max_size_) {
|
||||
clear();
|
||||
}
|
||||
lock(bucket);
|
||||
unlock(&clear_bucket_);
|
||||
}
|
||||
|
||||
// Allocate a new entry, set the key and value, then set the next pointer as the current
|
||||
// first entry in the bucket then make this new entry the first in the bucket.
|
||||
struct entry *new_entry = (struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
new_entry->key = key;
|
||||
new_entry->value = value;
|
||||
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
|
||||
OSIncrementAtomic(&count_);
|
||||
|
||||
unlock(bucket);
|
||||
return zero_;
|
||||
}
|
||||
|
||||
/**
|
||||
An alias for `set(key, zero_)`
|
||||
*/
|
||||
inline void remove(uint64_t key) {
|
||||
set(key, zero_);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove all entries and free bucket memory.
|
||||
*/
|
||||
void clear() {
|
||||
for (uint32_t i = 0; i < bucket_count_; ++i) {
|
||||
struct bucket *bucket = &buckets_[i];
|
||||
// We grab the lock so nothing can use this bucket while we're erasing it
|
||||
// and never release it. It'll be 'released' when the bzero call happens
|
||||
// at the end of this function.
|
||||
lock(bucket);
|
||||
|
||||
// Free the bucket's entries, if there are any.
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
struct entry *next_entry = entry->next;
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
entry = next_entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset cache count, no atomicity needed as we hold all the bucket locks.
|
||||
count_ = 0;
|
||||
|
||||
// This resets all of the bucket counts and locks. Releasing the locks for
|
||||
// each bucket isn't really atomic here but each bucket will be zero'd
|
||||
// before the lock is released as the lock is the last thing in a bucket.
|
||||
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
/**
|
||||
Return number of entries currently in cache.
|
||||
*/
|
||||
inline uint64_t count() const {
|
||||
return count_;
|
||||
}
|
||||
|
||||
private:
|
||||
struct entry {
|
||||
uint64_t key;
|
||||
T value;
|
||||
struct entry *next;
|
||||
};
|
||||
|
||||
struct bucket {
|
||||
// The least significant bit of this pointer is always 0 (due to alignment),
|
||||
// so we utilize that bit as the lock for the bucket.
|
||||
struct entry *head;
|
||||
};
|
||||
|
||||
/**
|
||||
Lock a bucket. Spins until the lock is acquired.
|
||||
*/
|
||||
inline void lock(struct bucket *bucket) const {
|
||||
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head));
|
||||
}
|
||||
|
||||
/**
|
||||
Unlock a bucket. Panics if the lock wasn't locked.
|
||||
*/
|
||||
inline void unlock(struct bucket *bucket) const {
|
||||
if (unlikely(OSTestAndClear(7, (volatile uint8_t *)&bucket->head))) {
|
||||
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t count_ = 0;
|
||||
|
||||
uint64_t max_size_;
|
||||
uint32_t bucket_count_;
|
||||
|
||||
struct bucket *buckets_;
|
||||
|
||||
/**
|
||||
Holder for a 'zero' entry for the current type
|
||||
*/
|
||||
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
|
||||
@@ -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
|
||||
@@ -24,40 +24,33 @@ bool SantaDecisionManager::init() {
|
||||
|
||||
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
|
||||
|
||||
sdm_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
vnode_pid_map_lock_ = lck_rw_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
|
||||
cached_decisions_ = OSDictionary::withCapacity(1000);
|
||||
vnode_pid_map_ = OSDictionary::withCapacity(1000);
|
||||
decision_cache_ = new SantaCache<uint64_t>(10000, 2);
|
||||
vnode_pid_map_ = new SantaCache<uint64_t>(2000, 5);
|
||||
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(kMaxDecisionQueueEvents,
|
||||
sizeof(santa_message_t));
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
if (!decision_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(kMaxLogQueueEvents,
|
||||
sizeof(santa_message_t));
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
if (!log_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
client_pid_ = 0;
|
||||
|
||||
ts_ = { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
|
||||
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::free() {
|
||||
if (cached_decisions_lock_) {
|
||||
lck_rw_free(cached_decisions_lock_, sdm_lock_grp_);
|
||||
cached_decisions_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (vnode_pid_map_lock_) {
|
||||
lck_rw_free(vnode_pid_map_lock_, sdm_lock_grp_);
|
||||
vnode_pid_map_lock_ = nullptr;
|
||||
}
|
||||
delete decision_cache_;
|
||||
delete vnode_pid_map_;
|
||||
|
||||
if (decision_dataqueue_lock_) {
|
||||
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
|
||||
@@ -86,8 +79,6 @@ void SantaDecisionManager::free() {
|
||||
|
||||
OSSafeReleaseNULL(decision_dataqueue_);
|
||||
OSSafeReleaseNULL(log_dataqueue_);
|
||||
OSSafeReleaseNULL(cached_decisions_);
|
||||
OSSafeReleaseNULL(vnode_pid_map_);
|
||||
|
||||
super::free();
|
||||
}
|
||||
@@ -97,12 +88,12 @@ void SantaDecisionManager::free() {
|
||||
void SantaDecisionManager::ConnectClient(pid_t pid) {
|
||||
if (!pid) return;
|
||||
|
||||
client_pid_ = pid;
|
||||
|
||||
// Any decisions made while the daemon wasn't
|
||||
// connected should be cleared
|
||||
ClearCache();
|
||||
|
||||
client_pid_ = pid;
|
||||
|
||||
failed_decision_queue_requests_ = 0;
|
||||
failed_log_queue_requests_ = 0;
|
||||
}
|
||||
@@ -136,6 +127,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 +160,12 @@ IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
|
||||
#pragma mark Listener Control
|
||||
|
||||
kern_return_t SantaDecisionManager::StartListener() {
|
||||
vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE,
|
||||
vnode_scope_callback,
|
||||
reinterpret_cast<void *>(this));
|
||||
vnode_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
|
||||
if (!vnode_listener_) return kIOReturnInternalError;
|
||||
|
||||
fileop_listener_ = kauth_listen_scope(KAUTH_SCOPE_FILEOP,
|
||||
fileop_scope_callback,
|
||||
reinterpret_cast<void *>(this));
|
||||
fileop_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_FILEOP, fileop_scope_callback, reinterpret_cast<void *>(this));
|
||||
if (!fileop_listener_) return kIOReturnInternalError;
|
||||
|
||||
LOGD("Listeners started.");
|
||||
@@ -206,131 +196,88 @@ kern_return_t SantaDecisionManager::StopListener() {
|
||||
#pragma mark Cache Management
|
||||
|
||||
void SantaDecisionManager::AddToCache(
|
||||
const char *identifier, santa_action_t decision, uint64_t microsecs) {
|
||||
if (cached_decisions_->getCount() > kMaxCacheSize) {
|
||||
// This could be made a _lot_ smarter, say only removing entries older
|
||||
// than a certain time period. However, with a kMaxCacheSize set
|
||||
// sufficiently large and a kMaxAllowCacheTimeMilliseconds set
|
||||
// sufficiently low, this should only ever occur if someone is purposefully
|
||||
// trying to make the cache grow.
|
||||
LOGI("Cache too large, flushing.");
|
||||
ClearCache();
|
||||
uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
|
||||
// If a previous entry was not found and the new entry is not `REQUEST_BINARY`, remove the
|
||||
// existing entry. This is to prevent adding an ALLOW to the cache after a write has occurred.
|
||||
if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
|
||||
decision_cache_->remove(identifier);
|
||||
}
|
||||
|
||||
if (decision == ACTION_REQUEST_BINARY) {
|
||||
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_);
|
||||
}
|
||||
wakeup((void *)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);
|
||||
wakeup((void *)identifier);
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::CacheCount() const {
|
||||
return cached_decisions_->getCount();
|
||||
return decision_cache_->count();
|
||||
}
|
||||
|
||||
void SantaDecisionManager::ClearCache() {
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
cached_decisions_->flushCollection();
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
decision_cache_->clear();
|
||||
}
|
||||
|
||||
#pragma mark Decision Fetching
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
santa_action_t 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;
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_sec_t secs = 0;
|
||||
clock_usec_t microsecs = 0;
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
uint64_t uptime = (secs * 1000000) + microsecs;
|
||||
#endif
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Add pending request to cache.
|
||||
AddToCache(vnode_id_str, ACTION_REQUEST_BINARY, 0);
|
||||
// Add pending request to cache, to be replaced by daemon with actual response
|
||||
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
|
||||
|
||||
// Send request to daemon...
|
||||
if (!PostToDecisionQueue(message)) {
|
||||
OSIncrementAtomic(&failed_decision_queue_requests_);
|
||||
if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
client_pid_ = 0;
|
||||
}
|
||||
LOGE("Failed to queue request for %s.", message->path);
|
||||
CacheCheck(vnode_id_str);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
do {
|
||||
IOSleep(kRequestLoopSleepMilliseconds);
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
|
||||
return_action = GetFromCache(identifier);
|
||||
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
|
||||
} while (!RESPONSE_VALID(return_action) && ClientConnected());
|
||||
|
||||
@@ -338,25 +285,40 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
if (!RESPONSE_VALID(return_action)) {
|
||||
LOGE("Daemon process did not respond correctly. Allowing executions "
|
||||
"until it comes back. Executable path: %s", message->path);
|
||||
CacheCheck(vnode_id_str);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
LOGD("Decision time: %4lldms (%s)",
|
||||
(((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
|
||||
#endif
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
const uint64_t vnode_id) {
|
||||
while (true) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id_str);
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id);
|
||||
|
||||
// If item was in cache return it.
|
||||
if (RESPONSE_VALID(return_action)) return return_action;
|
||||
// If item was in cache with a valid response, return it.
|
||||
// If item is in cache but hasn't received a response yet, sleep for a bit.
|
||||
// If item is not in cache, break out of loop to send request to daemon.
|
||||
if (RESPONSE_VALID(return_action)) {
|
||||
return return_action;
|
||||
} else if (return_action == ACTION_REQUEST_BINARY) {
|
||||
msleep((void *)vnode_id, NULL, 0, "", &ts_);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
@@ -370,7 +332,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
message->action = ACTION_REQUEST_BINARY;
|
||||
message->vnode_id = vnode_id;
|
||||
proc_name(message->ppid, message->pname, sizeof(message->pname));
|
||||
return_action = GetFromDaemon(message, vnode_id_str);
|
||||
auto return_action = GetFromDaemon(message, vnode_id);
|
||||
delete message;
|
||||
return return_action;
|
||||
}
|
||||
@@ -380,6 +342,14 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (++failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d decision requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
client_pid_ = 0;
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
@@ -388,7 +358,7 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (OSCompareAndSwap(0, 1, &failed_log_queue_requests_)) {
|
||||
if (failed_log_queue_requests_++ == 0) {
|
||||
LOGW("Dropping log queue messages");
|
||||
}
|
||||
// If enqueue failed, pop an item off the queue and try again.
|
||||
@@ -396,7 +366,9 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
log_dataqueue_->dequeue(0, &dataSize);
|
||||
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
} else {
|
||||
OSCompareAndSwap(1, 0, &failed_log_queue_requests_);
|
||||
if (failed_log_queue_requests_ > 0) {
|
||||
failed_log_queue_requests_--;
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return kr;
|
||||
@@ -418,22 +390,17 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vfs_context_t ctx,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Only operate on regular files (not directories, symlinks, etc.).
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Get ID for the vnode and convert it to a string.
|
||||
// Get ID for the vnode
|
||||
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
char vnode_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
// Fetch decision
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id);
|
||||
|
||||
// If file has dirty blocks, remove from cache and deny. This would usually
|
||||
// be the case if a file has been written to and flushed but not yet
|
||||
// closed.
|
||||
if (vnode_hasdirtyblks(vp)) {
|
||||
CacheCheck(vnode_str);
|
||||
RemoveFromCache(vnode_id);
|
||||
returnedAction = ACTION_RESPOND_DENY;
|
||||
}
|
||||
|
||||
@@ -441,16 +408,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_);
|
||||
if (vnode_pid_map_->getCount() > 5000) {
|
||||
vnode_pid_map_->flushCollection();
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -468,33 +430,26 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (!ClientConnected() || proc_selfpid() == client_pid_) return;
|
||||
|
||||
if (vp) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
auto vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
vfs_context_rele(context);
|
||||
|
||||
if (action == KAUTH_FILEOP_CLOSE) {
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
CacheCheck(vnode_id_str);
|
||||
RemoveFromCache(vnode_id);
|
||||
} else if (action == KAUTH_FILEOP_EXEC) {
|
||||
auto message = NewMessage(nullptr);
|
||||
message->vnode_id = vnode_id;
|
||||
message->action = ACTION_NOTIFY_EXEC;
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
|
||||
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;
|
||||
@@ -503,8 +458,7 @@ void SantaDecisionManager::FileOpCallback(
|
||||
|
||||
// Filter out modifications to locations that are definitely
|
||||
// not useful or made by santad.
|
||||
if (proc_selfpid() != client_pid_ &&
|
||||
!strprefix(path, "/.") && !strprefix(path, "/dev")) {
|
||||
if (!strprefix(path, "/.") && !strprefix(path, "/dev")) {
|
||||
auto message = NewMessage(nullptr);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
|
||||
@@ -526,7 +480,9 @@ void SantaDecisionManager::FileOpCallback(
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
message->action = ACTION_NOTIFY_DELETE;
|
||||
break;
|
||||
default: delete message; return;
|
||||
default:
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
@@ -542,14 +498,16 @@ extern "C" int fileop_scope_callback(
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (unlikely(sdm == nullptr)) {
|
||||
LOGE("fileop_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = nullptr;
|
||||
char *path = nullptr;
|
||||
char *new_path = nullptr;
|
||||
|
||||
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);
|
||||
@@ -576,20 +534,35 @@ extern "C" int fileop_scope_callback(
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
if (action & KAUTH_VNODE_ACCESS ||
|
||||
!(action & KAUTH_VNODE_EXECUTE) ||
|
||||
idata == nullptr) {
|
||||
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 (unlikely(sdm == nullptr)) {
|
||||
LOGE("vnode_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
|
||||
|
||||
// We only care about regular files.
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
if ((action & KAUTH_VNODE_EXECUTE) && !(action & KAUTH_VNODE_ACCESS)) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
vp,
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
} else if (action & KAUTH_VNODE_WRITE_DATA) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
char path[MAXPATHLEN];
|
||||
int pathlen = MAXPATHLEN;
|
||||
vn_getpath(vp, path, &pathlen);
|
||||
sdm->FileOpCallback(KAUTH_FILEOP_CLOSE, vp, path, nullptr);
|
||||
sdm->DecrementListenerInvocations();
|
||||
}
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
/**
|
||||
While waiting for a response from the daemon, this is the maximum number of
|
||||
milliseconds to sleep for before checking the cache for a response.
|
||||
*/
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
|
||||
|
||||
/// The maximum number of milliseconds a cached deny message should be considered valid.
|
||||
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 10;
|
||||
|
||||
///
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
static const uint32_t kMaxLogQueueEvents = 1024;
|
||||
/// The maximum number of messages can be kept in the logging data queue at any time.
|
||||
static const uint32_t kMaxLogQueueEvents = 2048;
|
||||
|
||||
/// Fetches a response from the 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.
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t FetchDecision(
|
||||
const kauth_cred_t cred, const vnode_t vp, const uint64_t vnode_id);
|
||||
|
||||
/**
|
||||
Posts the requested message to the decision data queue.
|
||||
|
||||
@param message The message to send
|
||||
@return bool true if sending was successful.
|
||||
*/
|
||||
bool PostToDecisionQueue(santa_message_t *message);
|
||||
|
||||
///
|
||||
/// Posts the requested message to the logging data queue.
|
||||
///
|
||||
/// @param message The message to send
|
||||
/// @return bool true if sending was successful.
|
||||
///
|
||||
/**
|
||||
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,11 +207,12 @@ 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.
|
||||
/// @param credential The kauth_cred_t for this action, if available.
|
||||
/// If nullptr, will get the credential for the current process.
|
||||
///
|
||||
/**
|
||||
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) {
|
||||
@@ -241,13 +229,13 @@ class SantaDecisionManager : public OSObject {
|
||||
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;
|
||||
@@ -256,22 +244,20 @@ 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_;
|
||||
int32_t failed_decision_queue_requests_;
|
||||
int32_t failed_log_queue_requests_;
|
||||
uint32_t failed_decision_queue_requests_;
|
||||
uint32_t failed_log_queue_requests_;
|
||||
|
||||
int32_t listener_invocations_;
|
||||
|
||||
@@ -279,34 +265,48 @@ class SantaDecisionManager : public OSObject {
|
||||
|
||||
kauth_listener_t vnode_listener_;
|
||||
kauth_listener_t fileop_listener_;
|
||||
|
||||
struct timespec ts_;
|
||||
};
|
||||
|
||||
///
|
||||
/// The kauth callback function for the Vnode scope
|
||||
/// @param actor's credentials
|
||||
/// @param data that was passed when the listener was registered
|
||||
/// @param action that was requested
|
||||
/// @param VFS context
|
||||
/// @param Vnode being operated on
|
||||
/// @param Parent Vnode. May be nullptr.
|
||||
/// @param Pointer to an errno-style error.
|
||||
///
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3);
|
||||
/**
|
||||
The kauth callback function for the Vnode scope
|
||||
|
||||
///
|
||||
/// The kauth callback function for the FileOp scope
|
||||
/// @param actor's credentials
|
||||
/// @param data that was passed when the listener was registered
|
||||
/// @param action that was requested
|
||||
/// @param depends on action, usually the vnode ref.
|
||||
/// @param depends on action.
|
||||
/// @param depends on action, usually 0.
|
||||
/// @param depends on action, usually 0.
|
||||
///
|
||||
@param actor's credentials
|
||||
@param data that was passed when the listener was registered
|
||||
@param action that was requested
|
||||
@param VFS context
|
||||
@param Vnode being operated on
|
||||
@param Parent Vnode. May be nullptr.
|
||||
@param Pointer to an errno-style error.
|
||||
*/
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential,
|
||||
void *idata,
|
||||
kauth_action_t action,
|
||||
uintptr_t arg0,
|
||||
uintptr_t arg1,
|
||||
uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
/**
|
||||
The kauth callback function for the FileOp scope
|
||||
|
||||
@param actor's credentials
|
||||
@param data that was passed when the listener was registered
|
||||
@param action that was requested
|
||||
@param depends on action, usually the vnode ref.
|
||||
@param depends on action.
|
||||
@param depends on action, usually 0.
|
||||
@param depends on action, usually 0.
|
||||
*/
|
||||
extern "C" int fileop_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3);
|
||||
kauth_cred_t credential,
|
||||
void *idata,
|
||||
kauth_action_t action,
|
||||
uintptr_t arg0,
|
||||
uintptr_t arg1,
|
||||
uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
|
||||
@@ -110,87 +110,74 @@ IOReturn SantaDriverClient::clientMemoryForType(
|
||||
|
||||
#pragma mark Callable Methods
|
||||
|
||||
IOReturn SantaDriverClient::open() {
|
||||
if (isInactive()) return kIOReturnNotAttached;
|
||||
IOReturn SantaDriverClient::open(
|
||||
OSObject *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (!myProvider->open(this)) {
|
||||
if (me->isInactive()) return kIOReturnNotAttached;
|
||||
if (!me->myProvider->open(me)) {
|
||||
LOGW("A second client tried to connect.");
|
||||
return kIOReturnExclusiveAccess;
|
||||
}
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_open(
|
||||
SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
return target->open();
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::allow_binary(const uint64_t vnode_id) {
|
||||
char vnode_id_str[21];
|
||||
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
|
||||
decisionManager->AddToCache(vnode_id_str, ACTION_RESPOND_ALLOW);
|
||||
LOGI("Client connected.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_allow_binary(
|
||||
SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
|
||||
IOReturn SantaDriverClient::allow_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
return target->allow_binary(
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::deny_binary(const uint64_t vnode_id) {
|
||||
char vnode_id_str[21];
|
||||
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
|
||||
decisionManager->AddToCache(vnode_id_str, ACTION_RESPOND_DENY);
|
||||
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
|
||||
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_deny_binary(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
|
||||
IOReturn SantaDriverClient::deny_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
return target->deny_binary(
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
|
||||
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
|
||||
|
||||
IOReturn SantaDriverClient::clear_cache() {
|
||||
decisionManager->ClearCache();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_clear_cache(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
return target->clear_cache();
|
||||
}
|
||||
IOReturn SantaDriverClient::clear_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
me->decisionManager->ClearCache();
|
||||
|
||||
IOReturn SantaDriverClient::cache_count(uint64_t *output) {
|
||||
*output = decisionManager->CacheCount();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_cache_count(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
return target->cache_count(&(arguments->scalarOutput[0]));
|
||||
IOReturn SantaDriverClient::cache_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
arguments->scalarOutput[0] = me->decisionManager->CacheCount();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::check_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
uint64_t input = *arguments->scalarInput;
|
||||
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(input);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
#pragma mark Method Resolution
|
||||
@@ -203,60 +190,23 @@ IOReturn SantaDriverClient::externalMethod(
|
||||
void *reference) {
|
||||
/// Array of methods callable by clients. The order of these must match the
|
||||
/// order of the items in SantaDriverMethods in SNTKernelCommon.h
|
||||
IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(&SantaDriverClient::static_open),
|
||||
0, // input scalar
|
||||
0, // input struct
|
||||
0, // output scalar
|
||||
0 // output struct
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_allow_binary),
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_deny_binary),
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_clear_cache),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_cache_count),
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0
|
||||
}
|
||||
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
|
||||
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
|
||||
{ &SantaDriverClient::open, 0, 0, 0, 0 },
|
||||
{ &SantaDriverClient::allow_binary, 1, 0, 0, 0 },
|
||||
{ &SantaDriverClient::deny_binary, 1, 0, 0, 0 },
|
||||
{ &SantaDriverClient::clear_cache, 0, 0, 0, 0 },
|
||||
{ &SantaDriverClient::cache_count, 0, 0, 1, 0 },
|
||||
{ &SantaDriverClient::check_cache, 1, 0, 1, 0 }
|
||||
};
|
||||
|
||||
if (selector < static_cast<UInt32>(kSantaUserClientNMethods)) {
|
||||
dispatch = &(sMethods[selector]);
|
||||
if (!target) target = this;
|
||||
} else {
|
||||
if (selector > static_cast<UInt32>(kSantaUserClientNMethods)) {
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
return super::externalMethod(selector,
|
||||
arguments,
|
||||
dispatch,
|
||||
target,
|
||||
reference);
|
||||
dispatch = &(sMethods[selector]);
|
||||
if (!target) target = this;
|
||||
return super::externalMethod(selector, arguments, dispatch, target, reference);
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
@@ -72,44 +72,33 @@ class com_google_SantaDriverClient : public IOUserClient {
|
||||
|
||||
///
|
||||
/// The userpsace callable methods are below. Each method corresponds
|
||||
/// to an entry in SantaDriverMethods. Each method has a static version
|
||||
/// which just calls the method on the provided target.
|
||||
/// to an entry in SantaDriverMethods.
|
||||
///
|
||||
|
||||
/// Called during client connection.
|
||||
IOReturn open();
|
||||
static IOReturn static_open(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn open(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a binary.
|
||||
IOReturn allow_binary(uint64_t vnode_id);
|
||||
static IOReturn static_allow_binary(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn allow_binary(
|
||||
OSObject *target, void *reference,IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to deny a binary.
|
||||
IOReturn deny_binary(uint64_t vnode_id);
|
||||
static IOReturn static_deny_binary(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn deny_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to empty the cache.
|
||||
IOReturn clear_cache();
|
||||
static IOReturn static_clear_cache(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn clear_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in the cache
|
||||
IOReturn cache_count(uint64_t *output);
|
||||
static IOReturn static_cache_count(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn cache_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out the status of a vnode_id in the cache.
|
||||
/// Output will be a santa_action_t.
|
||||
static IOReturn check_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
private:
|
||||
com_google_SantaDriver *myProvider;
|
||||
|
||||
71
Source/santactl/Commands/SNTCommandCheckCache.m
Normal file
71
Source/santactl/Commands/SNTCommandCheckCache.m
Normal 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
|
||||
@@ -14,22 +14,358 @@
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
// file info keys
|
||||
static NSString *const kPath = @"Path";
|
||||
static NSString *const kBundleName = @"Bundle Name";
|
||||
static NSString *const kBundleVersion = @"Bundle Version";
|
||||
static NSString *const kBundleVersionStr = @"Bundle Version Str";
|
||||
static NSString *const kDownloadReferrerURL = @"Download Referrer URL";
|
||||
static NSString *const kDownloadURL = @"Download URL";
|
||||
static NSString *const kDownloadTimestamp = @"Download Timestamp";
|
||||
static NSString *const kDownloadAgent = @"Download Agent";
|
||||
static NSString *const kType = @"Type";
|
||||
static NSString *const kPageZero = @"Page Zero";
|
||||
static NSString *const kCodeSigned = @"Code-signed";
|
||||
static NSString *const kRule = @"Rule";
|
||||
static NSString *const kSigningChain = @"Signing Chain";
|
||||
|
||||
// signing chain keys
|
||||
static NSString *const kCommonName = @"Common Name";
|
||||
static NSString *const kOrganization = @"Organization";
|
||||
static NSString *const kOrganizationalUnit = @"Organizational Unit";
|
||||
static NSString *const kValidFrom = @"Valid From";
|
||||
static NSString *const kValidUntil = @"Valid Until";
|
||||
|
||||
// shared file info & signing chain keys
|
||||
static NSString *const kSHA256 = @"SHA-256";
|
||||
static NSString *const kSHA1 = @"SHA-1";
|
||||
|
||||
// global json output flag
|
||||
static BOOL json = NO;
|
||||
|
||||
BOOL PrettyOutput() {
|
||||
static int tty = 0;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
tty = isatty(STDOUT_FILENO);
|
||||
});
|
||||
return (tty && !json);
|
||||
}
|
||||
|
||||
#pragma mark SNTCommandFileInfo
|
||||
|
||||
@interface SNTCommandFileInfo : NSObject<SNTCommand>
|
||||
|
||||
@property(nonatomic) SNTXPCConnection *daemonConn;
|
||||
@property(nonatomic) SNTFileInfo *fileInfo;
|
||||
@property(nonatomic) MOLCodesignChecker *csc;
|
||||
|
||||
// file path used for object initialization
|
||||
@property(readonly, nonatomic) NSString *filePath;
|
||||
|
||||
// Block type to be used with propertyMap values
|
||||
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *);
|
||||
|
||||
// on read generated properties
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock path;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock sha256;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock sha1;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleName;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleVersion;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleShortVersionString;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadReferrerURL;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadURL;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadTimestamp;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadAgent;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock type;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock pageZero;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock rule;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock signingChain;
|
||||
|
||||
// Mapping between property string keys and SNTAttributeBlocks
|
||||
@property(nonatomic) NSMutableDictionary<NSString *, SNTAttributeBlock> *propertyMap;
|
||||
|
||||
// Common Date Formatter
|
||||
@property(nonatomic) NSDateFormatter *dateFormatter;
|
||||
|
||||
// Block Helpers
|
||||
- (NSString *)humanReadableFileType:(SNTFileInfo *)fi;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTCommandFileInfo
|
||||
|
||||
REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath
|
||||
daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_daemonConn = daemonConn;
|
||||
_dateFormatter = [[NSDateFormatter alloc] init];
|
||||
_dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
_propertyMap = @{ kPath : self.path,
|
||||
kSHA256 : self.sha256,
|
||||
kSHA1 : self.sha1,
|
||||
kBundleName : self.bundleName,
|
||||
kBundleVersion : self.bundleVersion,
|
||||
kBundleVersionStr : self.bundleVersionStr,
|
||||
kDownloadReferrerURL : self.downloadReferrerURL,
|
||||
kDownloadURL : self.downloadURL,
|
||||
kDownloadTimestamp : self.downloadTimestamp,
|
||||
kDownloadAgent : self.downloadAgent,
|
||||
kType : self.type,
|
||||
kPageZero : self.pageZero,
|
||||
kCodeSigned : self.codeSigned,
|
||||
kRule : self.rule,
|
||||
kSigningChain : self.signingChain }.mutableCopy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark property getters
|
||||
|
||||
- (SNTFileInfo *)fileInfo {
|
||||
if (!_fileInfo) {
|
||||
_fileInfo = [[SNTFileInfo alloc] initWithPath:self.filePath];
|
||||
if (!_fileInfo) {
|
||||
fprintf(stderr, "\rInvalid or empty file: %s\n", self.filePath.UTF8String);
|
||||
}
|
||||
}
|
||||
return _fileInfo;
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)path {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.path;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)sha256 {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.SHA256;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)sha1 {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.SHA1;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleName {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.bundleName;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleVersion {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.bundleVersion;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleVersionStr {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.bundleShortVersionString;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadReferrerURL {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.quarantineRefererURL;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadURL {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.quarantineDataURL;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadTimestamp {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return [fi.dateFormatter stringFromDate:fi.fileInfo.quarantineTimestamp];
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadAgent {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.quarantineAgentBundleID;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)type {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
NSArray *archs = [fi.fileInfo architectures];
|
||||
if (archs.count == 0) {
|
||||
return [fi humanReadableFileType:fi.fileInfo];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%@ (%@)",
|
||||
[fi humanReadableFileType:fi.fileInfo], [archs componentsJoinedByString:@", "]];
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)pageZero {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
if ([fi.fileInfo isMissingPageZero]) {
|
||||
return @"__PAGEZERO segment missing/bad!";
|
||||
}
|
||||
return nil;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)codeSigned {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
return @"No";
|
||||
case errSecCSSignatureFailed:
|
||||
case errSecCSStaticCodeChanged:
|
||||
case errSecCSSignatureNotVerifiable:
|
||||
case errSecCSSignatureUnsupported:
|
||||
return @"Yes, but code/signature changed/unverifiable";
|
||||
case errSecCSResourceDirectoryFailed:
|
||||
case errSecCSResourceNotSupported:
|
||||
case errSecCSResourceRulesInvalid:
|
||||
case errSecCSResourcesInvalid:
|
||||
case errSecCSResourcesNotFound:
|
||||
case errSecCSResourcesNotSealed:
|
||||
return @"Yes, but resources invalid";
|
||||
case errSecCSReqFailed:
|
||||
case errSecCSReqInvalid:
|
||||
case errSecCSReqUnsupported:
|
||||
return @"Yes, but failed requirement validation";
|
||||
case errSecCSInfoPlistFailed:
|
||||
return @"Yes, but can't validate as Info.plist is missing";
|
||||
default: {
|
||||
return [NSString stringWithFormat:@"Yes, but failed to validate (%ld)", error.code];
|
||||
}
|
||||
}
|
||||
} else if (fi.csc.signatureFlags & kSecCodeSignatureAdhoc) {
|
||||
return @"Yes, but ad-hoc";
|
||||
} else {
|
||||
return @"Yes";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)rule {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
__block SNTEventState s;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[fi.daemonConn resume];
|
||||
});
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
if (!fi.csc) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.path(fi) error:&error];
|
||||
}
|
||||
[[fi.daemonConn remoteObjectProxy] decisionForFilePath:fi.path(fi)
|
||||
fileSHA256:fi.propertyMap[kSHA256](fi)
|
||||
signingCertificate:fi.csc.leafCertificate
|
||||
reply:^(SNTEventState state) {
|
||||
if (state) s = state;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
return @"Cannot communicate with daemon";
|
||||
} else {
|
||||
NSMutableString *output =
|
||||
(SNTEventStateAllow & s) ? @"Whitelisted".mutableCopy : @"Blacklisted".mutableCopy;
|
||||
switch (s) {
|
||||
case SNTEventStateAllowUnknown:
|
||||
case SNTEventStateBlockUnknown:
|
||||
[output appendString:@" (Unknown)"];
|
||||
break;
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateBlockBinary:
|
||||
[output appendString:@" (Binary)"];
|
||||
break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateBlockCertificate:
|
||||
[output appendString:@" (Certificate)"];
|
||||
break;
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateBlockScope:
|
||||
[output appendString:@" (Scope)"];
|
||||
break;
|
||||
default:
|
||||
output = @"None".mutableCopy;
|
||||
break;
|
||||
}
|
||||
if (PrettyOutput()) {
|
||||
if ((SNTEventStateAllow & s)) {
|
||||
[output insertString:@"\033[32m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
} else if ((SNTEventStateBlock & s)) {
|
||||
[output insertString:@"\033[31m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
} else {
|
||||
[output insertString:@"\033[33m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
}
|
||||
}
|
||||
return output.copy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)signingChain {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
if (!fi.csc) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
|
||||
}
|
||||
if (fi.csc.certificates.count) {
|
||||
NSMutableArray *certs = [[NSMutableArray alloc] initWithCapacity:fi.csc.certificates.count];
|
||||
[fi.csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c, unsigned long idx,
|
||||
BOOL *stop) {
|
||||
[certs addObject:@{ kSHA256 : c.SHA256 ?: @"null",
|
||||
kSHA1 : c.SHA1 ?: @"null",
|
||||
kCommonName : c.commonName ?: @"null",
|
||||
kOrganization : c.orgName ?: @"null",
|
||||
kOrganizationalUnit : c.orgUnit ?: @"null",
|
||||
kValidFrom : [fi.dateFormatter stringFromDate:c.validFrom] ?: @"null",
|
||||
kValidUntil : [fi.dateFormatter stringFromDate:c.validUntil]
|
||||
?: @"null"
|
||||
}];
|
||||
}];
|
||||
return certs;
|
||||
}
|
||||
return nil;
|
||||
};
|
||||
}
|
||||
|
||||
- (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
|
||||
if ([fi isScript]) return @"Script";
|
||||
if ([fi isExecutable]) return @"Executable";
|
||||
if ([fi isDylib]) return @"Dynamic Library";
|
||||
if ([fi isBundle]) return @"Bundle/Plugin";
|
||||
if ([fi isKext]) return @"Kernel Extension";
|
||||
if ([fi isXARArchive]) return @"XAR Archive";
|
||||
if ([fi isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
}
|
||||
|
||||
#pragma mark SNTCommand protocol methods
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
@@ -43,189 +379,251 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"The details provided will be the same ones Santa uses to make a decision\n"
|
||||
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
|
||||
@"the type of file.");
|
||||
return [NSString stringWithFormat:
|
||||
@"The details provided will be the same ones Santa uses to make a decision\n"
|
||||
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
|
||||
@"the type of file."
|
||||
@"\n"
|
||||
@"Usage: santactl fileinfo [options] [file-paths]\n"
|
||||
@" --json: output in json format\n"
|
||||
@" --key: search and return this one piece of information\n"
|
||||
@" valid Keys:\n"
|
||||
@"%@\n"
|
||||
@" valid keys when using --cert-index:\n"
|
||||
@"%@\n"
|
||||
@" --cert-index: an integer corresponding to a certificate of the signing chain\n"
|
||||
@" 1 for the leaf certificate\n"
|
||||
@" -1 for the root certificate\n"
|
||||
@" 2 and up for the intermediates / root\n"
|
||||
@"\n"
|
||||
@"Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo /usr/bin/yes /bin/*\n",
|
||||
[self printKeyArray:[self fileInfoKeys]],
|
||||
[self printKeyArray:[self signingChainKeys]]];
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
NSString *filePath = [arguments firstObject];
|
||||
if (!arguments.count) [self printErrorUsageAndExit:@"No arguments"];
|
||||
|
||||
if (!filePath) {
|
||||
printf("Missing file path\n");
|
||||
exit(1);
|
||||
}
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
NSArray *filePaths;
|
||||
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
|
||||
if (!fileInfo) {
|
||||
printf("Invalid or empty file\n");
|
||||
exit(1);
|
||||
}
|
||||
[self parseArguments:arguments
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&json
|
||||
filePaths:&filePaths];
|
||||
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
// Only access outputHashes from the outputHashesQueue
|
||||
__block NSMutableArray *outputHashes = [[NSMutableArray alloc] initWithCapacity:filePaths.count];
|
||||
dispatch_group_t outputHashesGroup = dispatch_group_create();
|
||||
dispatch_queue_t outputHashesQueue =
|
||||
dispatch_queue_create("com.google.santa.outputhashes", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
if (isatty(STDOUT_FILENO)) printf("Hashing...");
|
||||
NSString *sha1, *sha256;
|
||||
[fileInfo hashSHA1:&sha1 SHA256:&sha256];
|
||||
if (isatty(STDOUT_FILENO)) printf("\r");
|
||||
__block NSOperationQueue *hashQueue = [[NSOperationQueue alloc] init];
|
||||
hashQueue.maxConcurrentOperationCount = 15;
|
||||
|
||||
[self printKey:@"Path" value:fileInfo.path];
|
||||
[self printKey:@"SHA-256" value:sha256];
|
||||
[self printKey:@"SHA-1" value:sha1];
|
||||
__block NSUInteger hashed = 0;
|
||||
|
||||
if (fileInfo.bundlePath) {
|
||||
[self printKey:@"Bundle Name" value:fileInfo.bundleName];
|
||||
[self printKey:@"Bundle Version" value:fileInfo.bundleVersion];
|
||||
[self printKey:@"Bundle Version Str" value:fileInfo.bundleShortVersionString];
|
||||
}
|
||||
[filePaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
NSBlockOperation *hashOperation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
if (PrettyOutput()) printf("\rCalculating %lu/%lu", ++hashed, filePaths.count);
|
||||
|
||||
if (fileInfo.quarantineDataURL) {
|
||||
[self printKey:@"Download Referer URL" value:fileInfo.quarantineRefererURL];
|
||||
[self printKey:@"Download URL" value:fileInfo.quarantineDataURL];
|
||||
[self printKey:@"Download Timestamp"
|
||||
value:[dateFormatter stringFromDate:fileInfo.quarantineTimestamp]];
|
||||
[self printKey:@"Download Agent" value:fileInfo.quarantineAgentBundleID];
|
||||
}
|
||||
SNTCommandFileInfo *fi = [[self alloc] initWithFilePath:obj daemonConnection:daemonConn];
|
||||
if (!fi.fileInfo) return;
|
||||
|
||||
NSArray *archs = [fileInfo architectures];
|
||||
if (archs.count == 0) {
|
||||
[self printKey:@"Type" value:[self humanReadableFileType:fileInfo]];
|
||||
exit(0);
|
||||
}
|
||||
__block NSMutableDictionary *outputHash = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSString *s = [NSString stringWithFormat:@"%@ (%@)",
|
||||
[self humanReadableFileType:fileInfo],
|
||||
[archs componentsJoinedByString:@", "]];
|
||||
[self printKey:@"Type" value:s];
|
||||
|
||||
if ([fileInfo isMissingPageZero]) {
|
||||
[self printKey:@"Page Zero" value:@"__PAGEZERO segment missing/bad!"];
|
||||
}
|
||||
|
||||
// Code signature state
|
||||
NSError *error;
|
||||
MOLCodesignChecker *csc = [[MOLCodesignChecker alloc] initWithBinaryPath:filePath error:&error];
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
[self printKey:@"Code-signed" value:@"No"];
|
||||
break;
|
||||
case errSecCSSignatureFailed:
|
||||
case errSecCSStaticCodeChanged:
|
||||
case errSecCSSignatureNotVerifiable:
|
||||
case errSecCSSignatureUnsupported:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but code/signature changed/unverifiable"];
|
||||
break;
|
||||
case errSecCSResourceDirectoryFailed:
|
||||
case errSecCSResourceNotSupported:
|
||||
case errSecCSResourceRulesInvalid:
|
||||
case errSecCSResourcesInvalid:
|
||||
case errSecCSResourcesNotFound:
|
||||
case errSecCSResourcesNotSealed:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but resources invalid"];
|
||||
break;
|
||||
case errSecCSReqFailed:
|
||||
case errSecCSReqInvalid:
|
||||
case errSecCSReqUnsupported:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but failed requirement validation"];
|
||||
break;
|
||||
case errSecCSInfoPlistFailed:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but can't validate as Info.plist is missing"];
|
||||
break;
|
||||
default: {
|
||||
NSString *val = [NSString stringWithFormat:@"Yes, but failed to validate (%ld)",
|
||||
error.code];
|
||||
[self printKey:@"Code-signed" value:val];
|
||||
break;
|
||||
if (key && !certIndex) {
|
||||
SNTAttributeBlock block = fi.propertyMap[key];
|
||||
outputHash[key] = block(fi);
|
||||
} else if (certIndex) {
|
||||
NSArray *signingChain = fi.signingChain(fi);
|
||||
if (key) {
|
||||
if ([certIndex isEqual:@(-1)]) {
|
||||
outputHash[key] = signingChain.lastObject[key];
|
||||
} else {
|
||||
if (certIndex.unsignedIntegerValue - 1 < signingChain.count) {
|
||||
outputHash[key] = signingChain[certIndex.unsignedIntegerValue - 1][key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ([certIndex isEqual:@(-1)]) {
|
||||
outputHash[kSigningChain] = @[ signingChain.lastObject ?: @{} ];
|
||||
} else {
|
||||
NSMutableArray *indexedCert = [NSMutableArray arrayWithCapacity:signingChain.count];
|
||||
[signingChain enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (certIndex.unsignedIntegerValue - 1 == idx) {
|
||||
[indexedCert addObject:obj];
|
||||
} else {
|
||||
[indexedCert addObject:[NSNull null]];
|
||||
}
|
||||
}];
|
||||
if (indexedCert.count) outputHash[kSigningChain] = indexedCert;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSString *sha1, *sha256;
|
||||
[fi.fileInfo hashSHA1:&sha1 SHA256:&sha256];
|
||||
fi.propertyMap[kSHA1] = ^id (SNTCommandFileInfo *fi) { return sha1; };
|
||||
fi.propertyMap[kSHA256] = ^id (SNTCommandFileInfo *fi) { return sha256; };
|
||||
[fi.propertyMap enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
SNTAttributeBlock block = obj;
|
||||
outputHash[key] = block(fi);
|
||||
}];
|
||||
}
|
||||
if (outputHash.count) {
|
||||
dispatch_group_async(outputHashesGroup, outputHashesQueue, ^{
|
||||
[outputHashes addObject:outputHash];
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (csc.signatureFlags & kSecCodeSignatureAdhoc) {
|
||||
[self printKey:@"Code-signed" value:@"Yes, but ad-hoc"];
|
||||
} else {
|
||||
[self printKey:@"Code-signed" value:@"Yes"];
|
||||
}
|
||||
|
||||
// Binary rule state
|
||||
__block SNTRule *r;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
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, 1 * NSEC_PER_SEC))) {
|
||||
[self printKey:@"Rule" value:@"Cannot communicate with daemon"];
|
||||
} else {
|
||||
NSString *output;
|
||||
switch (r.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
output = @"Whitelisted";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[32mWhitelisted\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
break;
|
||||
case SNTRuleStateBlacklist:
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
output = @"Blacklisted";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[31mBlacklisted\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
break;
|
||||
default:
|
||||
output = @"None";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[33mNone\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
}
|
||||
}
|
||||
|
||||
// Signing chain
|
||||
if (csc.certificates.count) {
|
||||
printf("Signing chain:\n");
|
||||
|
||||
[csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c,
|
||||
unsigned long idx,
|
||||
BOOL *stop) {
|
||||
printf(" %2lu. %-20s: %s\n", idx + 1, "SHA-256", [c.SHA256 UTF8String]);
|
||||
printf(" %-20s: %s\n", "SHA-1", [c.SHA1 UTF8String]);
|
||||
printf(" %-20s: %s\n", "Common Name", [c.commonName UTF8String]);
|
||||
printf(" %-20s: %s\n", "Organization", [c.orgName UTF8String]);
|
||||
printf(" %-20s: %s\n", "Organizational Unit", [c.orgUnit UTF8String]);
|
||||
printf(" %-20s: %s\n", "Valid From",
|
||||
[[dateFormatter stringFromDate:c.validFrom] UTF8String]);
|
||||
printf(" %-20s: %s\n", "Valid Until",
|
||||
[[dateFormatter stringFromDate:c.validUntil] UTF8String]);
|
||||
printf("\n");
|
||||
}];
|
||||
}
|
||||
|
||||
hashOperation.qualityOfService = NSQualityOfServiceUserInitiated;
|
||||
[hashQueue addOperation:hashOperation];
|
||||
}];
|
||||
|
||||
// Wait for all the calculating threads to finish
|
||||
[hashQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// Clear the "Calculating ..." indicator if present
|
||||
if (PrettyOutput()) printf("\33[2K\r");
|
||||
|
||||
// Wait for all the writes to the outputHashes to finish
|
||||
dispatch_group_wait(outputHashesGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (outputHashes.count) [self printOutputHashes:outputHashes];
|
||||
exit(0);
|
||||
}
|
||||
|
||||
+ (void)printKey:(NSString *)key value:(NSString *)value {
|
||||
if (!key || !value) return;
|
||||
#pragma mark FileInfo helper methods
|
||||
|
||||
+ (NSArray *)fileInfoKeys {
|
||||
return @[ kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr,
|
||||
kDownloadReferrerURL, kDownloadURL, kDownloadTimestamp, kDownloadAgent,
|
||||
kType, kPageZero, kCodeSigned, kRule, kSigningChain ];
|
||||
}
|
||||
|
||||
+ (NSArray *)signingChainKeys {
|
||||
return @[ kSHA256, kSHA1, kCommonName, kOrganization, kOrganizationalUnit, kValidFrom,
|
||||
kValidUntil ];
|
||||
}
|
||||
|
||||
+ (NSString *)printKeyArray:(NSArray *)array {
|
||||
__block NSMutableString *string = [[NSMutableString alloc] init];
|
||||
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
[string appendString:[NSString stringWithFormat:@" \"%@\"\n", obj]];
|
||||
}];
|
||||
return string;
|
||||
}
|
||||
|
||||
+ (void)printErrorUsageAndExit:(NSString *)error {
|
||||
printf("%s\n\n", [error UTF8String]);
|
||||
printf("%s\n", [[self longHelpText] UTF8String]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
+ (void)parseArguments:(NSArray *)args
|
||||
forKey:(NSString **)key
|
||||
certIndex:(NSNumber **)certIndex
|
||||
jsonOutput:(BOOL *)jsonOutput
|
||||
filePaths:(NSArray **)filePaths {
|
||||
__block NSMutableArray *paths = [[NSMutableArray alloc] init];
|
||||
[args enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([obj caseInsensitiveCompare:@"--json"] == NSOrderedSame) {
|
||||
*jsonOutput = YES;
|
||||
} else if ([obj caseInsensitiveCompare:@"--cert-index"] == NSOrderedSame) {
|
||||
if (++idx > args.count - 1 || [args[idx] hasPrefix:@"--"]) {
|
||||
[self printErrorUsageAndExit:@"\n--cert-index requires an argument"];
|
||||
}
|
||||
*certIndex = @([args[idx] integerValue]);
|
||||
} else if ([obj caseInsensitiveCompare:@"--key"] == NSOrderedSame) {
|
||||
if (++idx > args.count - 1 || [args[idx] hasPrefix:@"--"]) {
|
||||
[self printErrorUsageAndExit:@"\n--key requires an argument"];
|
||||
}
|
||||
*key = args[idx];
|
||||
} else if ([@([obj integerValue]) isEqual:*certIndex] || [obj isEqual:*key]) {
|
||||
return;
|
||||
} else {
|
||||
[paths addObject:args[idx]];
|
||||
}
|
||||
}];
|
||||
if (*key && !*certIndex && ![self.fileInfoKeys containsObject:*key]) {
|
||||
[self printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid key", *key]];
|
||||
} else if (*key && *certIndex && ![self.signingChainKeys containsObject:*key]) {
|
||||
[self printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid key when using --cert-index", *key]];
|
||||
} else if ([@(0) isEqual:*certIndex]) {
|
||||
[self printErrorUsageAndExit:@"\n0 is an invalid --cert-index\n --cert-index is 1 indexed"];
|
||||
}
|
||||
if (!paths.count) [self printErrorUsageAndExit:@"\nat least one file-path is needed"];
|
||||
*filePaths = paths.copy;
|
||||
}
|
||||
|
||||
+ (void)printOutputHashes:(NSArray *)outputHashes {
|
||||
if (json) {
|
||||
id object = (outputHashes.count > 1) ? outputHashes : outputHashes.firstObject;
|
||||
if (!object) return;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:NULL];
|
||||
printf("%s\n", [[NSString alloc] initWithData:jsonData
|
||||
encoding:NSUTF8StringEncoding].UTF8String);
|
||||
return;
|
||||
}
|
||||
|
||||
[outputHashes enumerateObjectsUsingBlock:^(id outputHash, NSUInteger idx, BOOL *stop) {
|
||||
if ([outputHash count] == 1) {
|
||||
return [self printValueFromOutputHash:outputHash];
|
||||
}
|
||||
[self.fileInfoKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
|
||||
[self printValueForKey:key fromOutputHash:outputHash];
|
||||
}];
|
||||
printf("\n");
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)printValueForKey:(NSString *)key fromOutputHash:(NSDictionary *)outputHash {
|
||||
id value = outputHash[key];
|
||||
if (!value) return;
|
||||
if ([key isEqualToString:kSigningChain]) {
|
||||
return [self printSigningChain:value];
|
||||
}
|
||||
printf("%-21s: %s\n", [key UTF8String], [value UTF8String]);
|
||||
}
|
||||
|
||||
+ (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
|
||||
if ([fi isScript]) return @"Script";
|
||||
if ([fi isExecutable]) return @"Executable";
|
||||
if ([fi isDylib]) return @"Dynamic Library";
|
||||
if ([fi isKext]) return @"Kernel Extension";
|
||||
if ([fi isXARArchive]) return @"XAR Archive";
|
||||
if ([fi isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
+ (void)printValueFromOutputHash:(NSDictionary *)outputHash {
|
||||
if ([[[outputHash allKeys] firstObject] isEqualToString:kSigningChain]) {
|
||||
return [self printSigningChain:[[outputHash allValues] firstObject]];
|
||||
}
|
||||
printf("%s\n", [[[outputHash allValues] firstObject] UTF8String]);
|
||||
}
|
||||
|
||||
+ (void)printSigningChain:(NSArray *)signingChain {
|
||||
if (!signingChain) return;
|
||||
printf("%s:\n", kSigningChain.UTF8String);
|
||||
__block int i = 0;
|
||||
[signingChain enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([obj isEqual:[NSNull null]]) return;
|
||||
if (i++) printf("\n");
|
||||
printf(" %2lu. %-20s: %s\n", idx + 1, kSHA256.UTF8String,
|
||||
((NSString *)obj[kSHA256]).UTF8String);
|
||||
printf(" %-20s: %s\n", kSHA1.UTF8String,
|
||||
((NSString *)obj[kSHA1]).UTF8String);
|
||||
printf(" %-20s: %s\n", kCommonName.UTF8String,
|
||||
((NSString *)obj[kCommonName]).UTF8String);
|
||||
printf(" %-20s: %s\n", kOrganization.UTF8String,
|
||||
((NSString *)obj[kOrganization]).UTF8String);
|
||||
printf(" %-20s: %s\n", kOrganizationalUnit.UTF8String,
|
||||
((NSString *)obj[kOrganizationalUnit]).UTF8String);
|
||||
printf(" %-20s: %s\n", kValidFrom.UTF8String,
|
||||
((NSString *)obj[kValidFrom]).UTF8String);
|
||||
printf(" %-20s: %s\n", kValidUntil.UTF8String,
|
||||
((NSString *)obj[kValidUntil]).UTF8String);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -121,6 +121,10 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
|
||||
if (path) {
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
|
||||
if (!fi.path) {
|
||||
[self printErrorUsageAndExit:@"Provided path was not a plain file"];
|
||||
}
|
||||
|
||||
if (newRule.type == SNTRuleTypeBinary) {
|
||||
newRule.shasum = fi.SHA256;
|
||||
} else if (newRule.type == SNTRuleTypeCertificate) {
|
||||
|
||||
@@ -102,9 +102,18 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] syncLastSuccess];
|
||||
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] fullSyncLastSuccess];
|
||||
NSString *lastSyncSuccessStr = [dateFormatter stringFromDate:lastSyncSuccess] ?: @"Never";
|
||||
NSDate *lastRuleSyncSuccess = [[SNTConfigurator configurator] ruleSyncLastSuccess];
|
||||
NSString *lastRuleSyncSuccessStr =
|
||||
[dateFormatter stringFromDate:lastRuleSyncSuccess] ?: lastSyncSuccessStr;
|
||||
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
|
||||
__block BOOL pushNotifications;
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] pushNotifications:^(BOOL response) {
|
||||
pushNotifications = response;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
// Wait a maximum of 5s for stats collected from daemon to arrive.
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
|
||||
@@ -114,7 +123,7 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
if ([arguments containsObject:@"--json"]) {
|
||||
NSDictionary *stats = @{
|
||||
@"daemon" : @{
|
||||
@"mode" : clientMode,
|
||||
@"mode" : clientMode ?: @"null",
|
||||
@"file_logging" : @(fileLogging),
|
||||
@"watchdog_cpu_events" : @(cpuEvents),
|
||||
@"watchdog_ram_events" : @(ramEvents),
|
||||
@@ -130,9 +139,11 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
@"events_pending_upload" : @(eventCount),
|
||||
},
|
||||
@"sync" : @{
|
||||
@"server" : syncURLStr,
|
||||
@"server" : syncURLStr ?: @"null",
|
||||
@"clean_required" : @(syncCleanReqd),
|
||||
@"last_successful" : lastSyncSuccessStr
|
||||
@"last_successful_full" : lastSyncSuccessStr ?: @"null",
|
||||
@"last_successful_rule" : lastRuleSyncSuccessStr ?: @"null",
|
||||
@"push_notifications" : pushNotifications ? @"Connected" : @"Disconnected"
|
||||
},
|
||||
};
|
||||
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
|
||||
@@ -142,22 +153,25 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf("%s\n", [statsStr UTF8String]);
|
||||
} else {
|
||||
printf(">>> Daemon Info\n");
|
||||
printf(" %-22s | %s\n", "Mode", [clientMode UTF8String]);
|
||||
printf(" %-22s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-22s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-22s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
|
||||
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
printf(">>> Kernel Info\n");
|
||||
printf(" %-22s | %lld\n", "Kernel cache count", cacheCount);
|
||||
printf(" %-25s | %lld\n", "Kernel cache count", cacheCount);
|
||||
printf(">>> Database Info\n");
|
||||
printf(" %-22s | %lld\n", "Binary Rules", binaryRuleCount);
|
||||
printf(" %-22s | %lld\n", "Certificate Rules", certRuleCount);
|
||||
printf(" %-22s | %lld\n", "Events Pending Upload", eventCount);
|
||||
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
|
||||
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
|
||||
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
|
||||
|
||||
if (syncURLStr) {
|
||||
printf(">>> Sync Info\n");
|
||||
printf(" %-22s | %s\n", "Sync Server", [syncURLStr UTF8String]);
|
||||
printf(" %-22s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
|
||||
printf(" %-22s | %s\n", "Last Successful Sync", [lastSyncSuccessStr UTF8String]);
|
||||
printf(" %-25s | %s\n", "Sync Server", [syncURLStr UTF8String]);
|
||||
printf(" %-25s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
|
||||
printf(" %-25s | %s\n", "Last Successful Full Sync", [lastSyncSuccessStr UTF8String]);
|
||||
printf(" %-25s | %s\n", "Last Successful Rule Sync", [lastRuleSyncSuccessStr UTF8String]);
|
||||
printf(" %-25s | %s\n", "Push Notifications",
|
||||
(pushNotifications ? "Connected" : "Disconnected"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,14 +14,7 @@
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
|
||||
#import <MOLAuthenticatingURLSession.h>
|
||||
|
||||
#import "SNTCommandSyncEventUpload.h"
|
||||
#import "SNTCommandSyncLogUpload.h"
|
||||
#import "SNTCommandSyncPostflight.h"
|
||||
#import "SNTCommandSyncPreflight.h"
|
||||
#import "SNTCommandSyncRuleDownload.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTCommandSyncManager.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTLogging.h"
|
||||
@@ -29,19 +22,22 @@
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTCommandSync : NSObject<SNTCommand>
|
||||
@property SNTCommandSyncState *syncState;
|
||||
@property SNTXPCConnection *listener;
|
||||
@property SNTCommandSyncManager *syncManager;
|
||||
@end
|
||||
|
||||
@implementation SNTCommandSync
|
||||
|
||||
REGISTER_COMMAND_NAME(@"sync")
|
||||
|
||||
#pragma mark SNTCommand protocol methods
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
@@ -63,36 +59,11 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
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;
|
||||
}];
|
||||
[daemonConn resume];
|
||||
BOOL daemon = [arguments containsObject:@"--daemon"];
|
||||
s.syncManager = [[SNTCommandSyncManager alloc] initWithDaemonConnection:daemonConn
|
||||
isDaemon:daemon];
|
||||
|
||||
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
|
||||
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
|
||||
@@ -100,138 +71,40 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
diskCapacity:0
|
||||
diskPath:nil]];
|
||||
|
||||
if (!s.syncManager.daemon) return [s.syncManager fullSync];
|
||||
[s syncdWithDaemonConnection:daemonConn];
|
||||
}
|
||||
|
||||
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
|
||||
authURLSession.userAgent = @"santactl-sync/";
|
||||
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
if (santactlVersion) {
|
||||
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
|
||||
}
|
||||
authURLSession.refusesRedirects = YES;
|
||||
authURLSession.serverHostname = s.syncState.syncBaseURL.host;
|
||||
authURLSession.loggingBlock = ^(NSString *line) {
|
||||
LOGD(@"%@", line);
|
||||
#pragma mark daemon methods
|
||||
|
||||
- (void)syncdWithDaemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
// Create listener for return connection from daemon.
|
||||
NSXPCListener *listener = [NSXPCListener anonymousListener];
|
||||
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
|
||||
self.listener.exportedInterface = [SNTXPCSyncdInterface syncdInterface];
|
||||
self.listener.exportedObject = self.syncManager;
|
||||
self.listener.acceptedHandler = ^{
|
||||
LOGD(@"santad <--> santactl connections established");
|
||||
dispatch_semaphore_signal(sema);
|
||||
};
|
||||
|
||||
// Configure server auth
|
||||
if ([config syncServerAuthRootsFile]) {
|
||||
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
|
||||
} else if ([config syncServerAuthRootsData]) {
|
||||
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
|
||||
}
|
||||
|
||||
// Configure client auth
|
||||
if ([config syncClientAuthCertificateFile]) {
|
||||
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
|
||||
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
|
||||
} else if ([config syncClientAuthCertificateCn]) {
|
||||
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
|
||||
} else if ([config syncClientAuthCertificateIssuer]) {
|
||||
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
|
||||
}
|
||||
|
||||
s.syncState.session = [authURLSession session];
|
||||
s.syncState.daemonConn = daemonConn;
|
||||
|
||||
if ([arguments containsObject:@"singleevent"]) {
|
||||
NSUInteger idx = [arguments indexOfObject:@"singleevent"] + 1;
|
||||
if (idx >= arguments.count) {
|
||||
LOGI(@"singleevent takes an argument");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
NSString *obj = arguments[idx];
|
||||
if (obj.length != 64) {
|
||||
LOGI(@"singleevent passed without SHA-256 as next argument");
|
||||
exit(1);
|
||||
}
|
||||
return [s eventUploadSingleEvent:obj];
|
||||
} else {
|
||||
return [s preflight];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)preflight {
|
||||
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Preflight complete");
|
||||
if (self.syncState.uploadLogURL) {
|
||||
return [self logUpload];
|
||||
} else {
|
||||
return [self eventUpload];
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Preflight failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logUpload {
|
||||
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Log upload complete");
|
||||
} else {
|
||||
LOGE(@"Log upload failed, continuing anyway");
|
||||
}
|
||||
return [self eventUpload];
|
||||
}
|
||||
|
||||
- (void)eventUpload {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Event upload complete");
|
||||
return [self ruleDownload];
|
||||
} else {
|
||||
LOGE(@"Event upload failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)eventUploadSingleEvent:(NSString *)sha256 {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p syncSingleEventWithSHA256:sha256]) {
|
||||
LOGD(@"Event upload complete");
|
||||
self.listener.invalidationHandler = ^{
|
||||
// If santad is unloaded kill santactl
|
||||
LOGD(@"exiting");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Event upload failed");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
[self.listener resume];
|
||||
|
||||
- (void)ruleDownload {
|
||||
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Rule download complete");
|
||||
if (self.syncState.bundleBinaryRequests.count) {
|
||||
return [self eventUploadBundleBinaries];
|
||||
}
|
||||
return [self postflight];
|
||||
} else {
|
||||
LOGE(@"Rule download failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// Tell daemon to connect back to the above listener.
|
||||
[[daemonConn remoteObjectProxy] setSyncdListener:listener.endpoint];
|
||||
|
||||
- (void)eventUploadBundleBinaries {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p syncBundleEvents]) {
|
||||
LOGD(@"Event Upload bundle binaries complete");
|
||||
} else {
|
||||
LOGW(@"Event Upload bundle binary search failed");
|
||||
// Now wait for the connection to come in.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
[self performSelectorInBackground:@selector(syncdWithDaemonConnection:) withObject:daemonConn];
|
||||
}
|
||||
return [self postflight];
|
||||
}
|
||||
|
||||
- (void)postflight {
|
||||
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Postflight complete");
|
||||
LOGI(@"Sync completed successfully");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Postflight failed");
|
||||
exit(1);
|
||||
}
|
||||
[self.syncManager fullSyncSecondsFromNow:15];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -31,6 +31,7 @@ extern NSString *const kWhitelistRegex;
|
||||
extern NSString *const kBlacklistRegex;
|
||||
extern NSString *const kBinaryRuleCount;
|
||||
extern NSString *const kCertificateRuleCount;
|
||||
extern NSString *const kFCMToken;
|
||||
|
||||
extern NSString *const kEvents;
|
||||
extern NSString *const kFileSHA256;
|
||||
@@ -88,3 +89,8 @@ extern NSString *const kRuleCustomMsg;
|
||||
extern NSString *const kCursor;
|
||||
|
||||
extern NSString *const kBackoffInterval;
|
||||
|
||||
extern NSString *const kFullSync;
|
||||
extern NSString *const kRuleSync;
|
||||
extern NSString *const kConfigSync;
|
||||
extern NSString *const kLogSync;
|
||||
|
||||
@@ -33,6 +33,7 @@ NSString *const kWhitelistRegex = @"whitelist_regex";
|
||||
NSString *const kBlacklistRegex = @"blacklist_regex";
|
||||
NSString *const kBinaryRuleCount = @"binary_rule_count";
|
||||
NSString *const kCertificateRuleCount = @"certificate_rule_count";
|
||||
NSString *const kFCMToken = @"fcm_token";
|
||||
|
||||
NSString *const kEvents = @"events";
|
||||
NSString *const kFileSHA256 = @"file_sha256";
|
||||
@@ -90,3 +91,8 @@ NSString *const kRuleCustomMsg = @"custom_msg";
|
||||
NSString *const kCursor = @"cursor";
|
||||
|
||||
NSString *const kBackoffInterval = @"backoff";
|
||||
|
||||
NSString *const kFullSync = @"full_sync";
|
||||
NSString *const kRuleSync = @"rule_sync";
|
||||
NSString *const kConfigSync = @"config_sync";
|
||||
NSString *const kLogSync = @"log_sync";
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
|
||||
|
||||
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256;
|
||||
- (BOOL)uploadEvents:(NSArray *)events;
|
||||
|
||||
- (BOOL)syncBundleEvents;
|
||||
|
||||
|
||||
@@ -44,21 +44,24 @@
|
||||
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256 {
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
[[self.daemonConn remoteObjectProxy] databaseEventForSHA256:sha256 reply:^(SNTStoredEvent *e) {
|
||||
if (e) {
|
||||
[self uploadEvents:@[ e ]];
|
||||
}
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)syncBundleEvents {
|
||||
NSMutableArray *newEvents = [NSMutableArray array];
|
||||
for (NSString *bundlePath in self.syncState.bundleBinaryRequests) {
|
||||
[newEvents addObjectsFromArray:[self findRelatedBinaries:bundlePath]];
|
||||
for (NSString *bundlePath in [NSSet setWithArray:self.syncState.bundleBinaryRequests]) {
|
||||
__block NSArray *relatedBinaries;
|
||||
__block BOOL shouldCancel = NO;
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
relatedBinaries = [self findRelatedBinaries:bundlePath shouldCancel:&shouldCancel];
|
||||
dispatch_semaphore_signal(sema);
|
||||
});
|
||||
|
||||
// Give the search up to 5m to run
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 300))) {
|
||||
LOGD(@"Timed out while searching for related binaries at path %@", bundlePath);
|
||||
shouldCancel = YES;
|
||||
} else {
|
||||
[newEvents addObjectsFromArray:relatedBinaries];
|
||||
}
|
||||
}
|
||||
return [self uploadEvents:newEvents];
|
||||
}
|
||||
@@ -66,10 +69,10 @@
|
||||
- (BOOL)uploadEvents:(NSArray *)events {
|
||||
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
|
||||
|
||||
NSMutableDictionary *eventIds = [NSMutableDictionary dictionaryWithCapacity:events.count];
|
||||
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:events.count];
|
||||
for (SNTStoredEvent *event in events) {
|
||||
[uploadEvents addObject:[self dictionaryForEvent:event]];
|
||||
eventIds[event.idx] = @YES;
|
||||
if (event.idx) [eventIds addObject:event.idx];
|
||||
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
|
||||
}
|
||||
|
||||
@@ -82,7 +85,7 @@
|
||||
LOGI(@"Uploaded %lu events", uploadEvents.count);
|
||||
|
||||
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
|
||||
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allKeys]];
|
||||
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
|
||||
|
||||
// See if there are any events remaining to upload
|
||||
if (uploadEvents.count < events.count) {
|
||||
@@ -158,37 +161,47 @@
|
||||
#undef ADDKEY
|
||||
}
|
||||
|
||||
// Find binaries within a bundle given the bundle's path
|
||||
// Searches for 10 minutes, creating new events.
|
||||
- (NSArray *)findRelatedBinaries:(NSString *)path {
|
||||
SNTFileInfo *requestedPath = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
// Prevent processing the same bundle twice.
|
||||
static NSMutableDictionary *previouslyProcessedBundles;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
previouslyProcessedBundles = [NSMutableDictionary dictionary];
|
||||
});
|
||||
if (previouslyProcessedBundles[requestedPath.bundleIdentifier]) return nil;
|
||||
previouslyProcessedBundles[requestedPath.bundleIdentifier] = @YES;
|
||||
/**
|
||||
Find binaries within a bundle given the bundle's path. Will run until completion, however long
|
||||
that might be. Search is done within the bundle concurrently, using up to 25 threads at once.
|
||||
|
||||
@param path, the path to begin searching underneath
|
||||
@param shouldCancel, if YES, the search is cancelled part way through.
|
||||
@return array of SNTStoredEvent's
|
||||
*/
|
||||
- (NSArray *)findRelatedBinaries:(NSString *)path shouldCancel:(BOOL *)shouldCancel {
|
||||
// For storing the generated events, with a simple lock for writing.
|
||||
NSMutableArray *relatedEvents = [NSMutableArray array];
|
||||
NSLock *relatedEventsLock = [[NSLock alloc] init];
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block BOOL shouldCancel = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
NSString *file;
|
||||
// Limit the number of threads that can process files at once to keep CPU usage down.
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(25);
|
||||
|
||||
while (file = [dirEnum nextObject]) {
|
||||
@autoreleasepool {
|
||||
if (shouldCancel) break;
|
||||
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
|
||||
// Group the processing into a single group so we can wait on the whole group at the end.
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
file = [path stringByAppendingPathComponent:file];
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
while (1) {
|
||||
@autoreleasepool {
|
||||
if (*shouldCancel) break;
|
||||
NSString *file = [dirEnum nextObject];
|
||||
if (!file) break;
|
||||
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
|
||||
|
||||
// Wait for a processing thread to become available
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
dispatch_group_async(group,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
|
||||
^{
|
||||
@autoreleasepool {
|
||||
NSString *newFile = [path stringByAppendingPathComponent:file];
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:newFile];
|
||||
if (!fi.isExecutable) {
|
||||
dispatch_semaphore_signal(sema);
|
||||
return;
|
||||
}
|
||||
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:file];
|
||||
if (fi.isExecutable) {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
se.filePath = fi.path;
|
||||
se.fileSHA256 = fi.SHA256;
|
||||
@@ -202,20 +215,18 @@
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
|
||||
se.signingChain = cs.certificates;
|
||||
|
||||
[relatedEvents addObject:[self dictionaryForEvent:se]];
|
||||
[relatedEventsLock lock];
|
||||
[relatedEvents addObject:se];
|
||||
[relatedEventsLock unlock];
|
||||
|
||||
dispatch_semaphore_signal(sema);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(sema);
|
||||
});
|
||||
|
||||
// Give the search up to 10m per bundle to run.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 600))) {
|
||||
shouldCancel = YES;
|
||||
LOGD(@"Timed out while searching for related events at path %@", path);
|
||||
}
|
||||
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return relatedEvents;
|
||||
}
|
||||
|
||||
|
||||
53
Source/santactl/Commands/sync/SNTCommandSyncManager.h
Normal file
53
Source/santactl/Commands/sync/SNTCommandSyncManager.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTXPCSyncdInterface.h"
|
||||
|
||||
@class SNTXPCConnection;
|
||||
|
||||
///
|
||||
/// Handles push notifications and periodic syncing with a sync server.
|
||||
///
|
||||
@interface SNTCommandSyncManager : NSObject<SNTSyncdXPC>
|
||||
|
||||
@property(readonly, nonatomic) BOOL daemon;
|
||||
|
||||
///
|
||||
/// Use the designated initializer initWithDaemonConnection:isDaemon:
|
||||
///
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
///
|
||||
/// @param daemonConn A connection to santad.
|
||||
/// @param daemon Set to YES if periodic syncing should occur.
|
||||
/// Set to NO if a single sync should be performed. NO is default.
|
||||
///
|
||||
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn
|
||||
isDaemon:(BOOL)daemon NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
///
|
||||
/// Perform a full sync immediately. Non-blocking.
|
||||
/// If a full sync is already running new requests will be dropped.
|
||||
///
|
||||
- (void)fullSync;
|
||||
|
||||
///
|
||||
/// Perform a full sync seconds from now. Non-blocking.
|
||||
/// If a full sync is already running new requests will be dropped.
|
||||
///
|
||||
- (void)fullSyncSecondsFromNow:(uint64_t)seconds;
|
||||
|
||||
@end
|
||||
475
Source/santactl/Commands/sync/SNTCommandSyncManager.m
Normal file
475
Source/santactl/Commands/sync/SNTCommandSyncManager.m
Normal file
@@ -0,0 +1,475 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommandSyncManager.h"
|
||||
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
|
||||
#import <MOLAuthenticatingURLSession.h>
|
||||
#import <MOLFCMClient/MOLFCMClient.h>
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncEventUpload.h"
|
||||
#import "SNTCommandSyncLogUpload.h"
|
||||
#import "SNTCommandSyncPostflight.h"
|
||||
#import "SNTCommandSyncPreflight.h"
|
||||
#import "SNTCommandSyncRuleDownload.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStrengthify.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
#import "SNTXPCSyncdInterface.h"
|
||||
|
||||
// Syncing time constants
|
||||
const uint64_t kFullSyncInterval = 600;
|
||||
const uint64_t kFullSyncFCMInterval = 14400;
|
||||
const uint64_t kGlobalRuleSyncLeeway = 600;
|
||||
|
||||
@interface SNTCommandSyncManager () {
|
||||
SCNetworkReachabilityRef _reachability;
|
||||
}
|
||||
@property(nonatomic) dispatch_source_t fullSyncTimer;
|
||||
@property(nonatomic) dispatch_source_t ruleSyncTimer;
|
||||
@property(nonatomic) NSCache *dispatchLock;
|
||||
@property(nonatomic) NSCache *ruleSyncCache;
|
||||
@property MOLFCMClient *FCMClient;
|
||||
@property(nonatomic) SNTXPCConnection *daemonConn;
|
||||
@property BOOL targetedRuleSync;
|
||||
@property(nonatomic) BOOL reachable;
|
||||
@end
|
||||
|
||||
// Called when the network state changes
|
||||
static void reachabilityHandler(
|
||||
SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
|
||||
SNTCommandSyncManager *commandSyncManager = (__bridge SNTCommandSyncManager *)info;
|
||||
// Only call the setter when there is a change. This will filter out the redundant calls to this
|
||||
// callback whenever the network interface states change.
|
||||
if (commandSyncManager.reachable != (flags & kSCNetworkReachabilityFlagsReachable)) {
|
||||
commandSyncManager.reachable = (flags & kSCNetworkReachabilityFlagsReachable);
|
||||
}
|
||||
}
|
||||
|
||||
@implementation SNTCommandSyncManager
|
||||
|
||||
#pragma mark init
|
||||
|
||||
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn isDaemon:(BOOL)daemon {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_daemonConn = daemonConn;
|
||||
_daemon = daemon;
|
||||
_fullSyncTimer = [self createSyncTimerWithBlock:^{
|
||||
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncFCMInterval];
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[self lockAction:kFullSync];
|
||||
[self preflight];
|
||||
[self unlockAction:kFullSync];
|
||||
}];
|
||||
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
|
||||
dispatch_source_set_timer(self.ruleSyncTimer,
|
||||
DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[self lockAction:kRuleSync];
|
||||
SNTCommandSyncState *syncState = [self createSyncState];
|
||||
syncState.targetedRuleSync = self.targetedRuleSync;
|
||||
syncState.ruleSyncCache = self.ruleSyncCache;
|
||||
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Rule download complete");
|
||||
} else {
|
||||
LOGE(@"Rule download failed");
|
||||
}
|
||||
self.targetedRuleSync = NO;
|
||||
[self unlockAction:kRuleSync];
|
||||
}];
|
||||
_dispatchLock = [[NSCache alloc] init];
|
||||
_ruleSyncCache = [[NSCache alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark SNTSyncdXPC protocol methods
|
||||
|
||||
- (void)postEventToSyncServer:(SNTStoredEvent *)event {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc]
|
||||
initWithState:[self createSyncState]];
|
||||
if (event && [p uploadEvents:@[event]]) {
|
||||
LOGD(@"Event upload complete");
|
||||
} else {
|
||||
LOGE(@"Event upload failed");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
|
||||
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
|
||||
}
|
||||
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply {
|
||||
reply((self.FCMClient.FCMToken != nil));
|
||||
}
|
||||
|
||||
#pragma mark push notification methods
|
||||
|
||||
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
|
||||
if ([self.FCMClient.FCMToken isEqualToString:syncState.FCMToken]) {
|
||||
LOGD(@"Continue with the current FCMToken");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD(@"Start listening for push notifications");
|
||||
|
||||
WEAKIFY(self);
|
||||
|
||||
[self.FCMClient disconnect];
|
||||
NSString *machineID = syncState.machineID;
|
||||
self.FCMClient = [[MOLFCMClient alloc] initWithFCMToken:syncState.FCMToken
|
||||
sessionConfiguration:syncState.session.configuration.copy
|
||||
messageHandler:^(NSDictionary *message) {
|
||||
if (!message || [message isEqual:@{}]) return;
|
||||
STRONGIFY(self);
|
||||
LOGD(@"%@", message);
|
||||
[self.FCMClient acknowledgeMessage:message];
|
||||
[self processFCMMessage:message withMachineID:machineID];
|
||||
}];
|
||||
|
||||
self.FCMClient.connectionErrorHandler = ^(NSError *error) {
|
||||
STRONGIFY(self);
|
||||
LOGE(@"FCM connection error: %@", error);
|
||||
[self.FCMClient disconnect];
|
||||
self.FCMClient = nil;
|
||||
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
|
||||
};
|
||||
|
||||
self.FCMClient.loggingBlock = ^(NSString *log) {
|
||||
LOGD(@"%@", log);
|
||||
};
|
||||
|
||||
[self.FCMClient connect];
|
||||
}
|
||||
|
||||
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
|
||||
NSData *entryData;
|
||||
|
||||
// Sort through the entries in the FCM message.
|
||||
for (NSDictionary *entry in FCMmessage[@"data"]) {
|
||||
if ([entry[@"key"] isEqualToString:@"blob"]) {
|
||||
entryData = [entry[@"value"] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entryData) {
|
||||
LOGD(@"Push notification message is not in the expected format...dropping message");
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *actionMessage = [NSJSONSerialization JSONObjectWithData:entryData
|
||||
options:NSJSONReadingAllowFragments
|
||||
error:&error];
|
||||
if (!actionMessage) {
|
||||
LOGD(@"Unable to parse push notification message value: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the file name and hash in a cache. When the rule is actually added, use the cache
|
||||
// to build a user notification.
|
||||
NSString *fileHash = actionMessage[@"file_hash"];
|
||||
NSString *fileName = actionMessage[@"file_name"];
|
||||
if (fileName && fileHash) {
|
||||
[self.ruleSyncCache setObject:fileName forKey:fileHash];
|
||||
}
|
||||
|
||||
NSString *action = actionMessage[@"action"];
|
||||
if (action) {
|
||||
LOGD(@"Push notification action: %@ received", action);
|
||||
} else {
|
||||
LOGD(@"Push notification message contains no action");
|
||||
}
|
||||
|
||||
if ([action isEqualToString:kFullSync]) {
|
||||
[self fullSync];
|
||||
} else if ([action isEqualToString:kRuleSync]) {
|
||||
NSString *targetMachineID = actionMessage[@"target_host_id"];
|
||||
if (![targetMachineID isKindOfClass:[NSNull class]] &&
|
||||
[targetMachineID.lowercaseString isEqualToString:machineID.lowercaseString]) {
|
||||
self.targetedRuleSync = YES;
|
||||
[self ruleSync];
|
||||
} else {
|
||||
uint32_t delaySeconds = arc4random_uniform(kGlobalRuleSyncLeeway);
|
||||
LOGD(@"Staggering rule download, %u second delay for this machine", delaySeconds);
|
||||
[self ruleSyncSecondsFromNow:delaySeconds];
|
||||
}
|
||||
} else if ([action isEqualToString:kConfigSync]) {
|
||||
[self fullSync];
|
||||
} else if ([action isEqualToString:kLogSync]) {
|
||||
[self fullSync];
|
||||
} else {
|
||||
LOGD(@"Unrecognised action: %@", action);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark sync timer control
|
||||
|
||||
- (void)fullSync {
|
||||
[self fullSyncSecondsFromNow:0];
|
||||
}
|
||||
|
||||
- (void)fullSyncSecondsFromNow:(uint64_t)seconds {
|
||||
if (![self checkLockAction:kFullSync]) {
|
||||
LOGD(@"%@ in progress, dropping reschedule request", kFullSync);
|
||||
return;
|
||||
}
|
||||
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
|
||||
}
|
||||
|
||||
- (void)ruleSync {
|
||||
[self ruleSyncSecondsFromNow:0];
|
||||
}
|
||||
|
||||
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds {
|
||||
if (![self checkLockAction:kRuleSync]) {
|
||||
LOGD(@"%@ in progress, dropping reschedule request", kRuleSync);
|
||||
return;
|
||||
}
|
||||
[self rescheduleTimerQueue:self.ruleSyncTimer secondsFromNow:seconds];
|
||||
}
|
||||
|
||||
- (void)rescheduleTimerQueue:(dispatch_source_t)timerQueue secondsFromNow:(uint64_t)seconds {
|
||||
uint64_t interval = seconds * NSEC_PER_SEC;
|
||||
uint64_t leeway = (seconds * 0.5) * NSEC_PER_SEC;
|
||||
dispatch_source_set_timer(timerQueue, dispatch_walltime(NULL, interval), interval, leeway);
|
||||
}
|
||||
|
||||
#pragma mark syncing chain
|
||||
|
||||
- (void)preflight {
|
||||
SNTCommandSyncState *syncState = [self createSyncState];
|
||||
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Preflight complete");
|
||||
|
||||
// Clean up reachability if it was started for a non-network error
|
||||
[self stopReachability];
|
||||
|
||||
// Start listening for push notifications with a full sync every kFullSyncFCMInterval or
|
||||
// revert to full syncing every kFullSyncInterval.
|
||||
if (syncState.daemon && syncState.FCMToken) {
|
||||
[self listenForPushNotificationsWithSyncState:syncState];
|
||||
} else if (syncState.daemon) {
|
||||
LOGD(@"FCMToken not provided. Sync every %llu min.", kFullSyncInterval / 60);
|
||||
[self.FCMClient disconnect];
|
||||
self.FCMClient = nil;
|
||||
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
|
||||
}
|
||||
|
||||
if (syncState.uploadLogURL) {
|
||||
return [self logUploadWithSyncState:syncState];
|
||||
} else {
|
||||
return [self eventUploadWithSyncState:syncState];
|
||||
}
|
||||
} else {
|
||||
if (!syncState.daemon) {
|
||||
LOGE(@"Preflight failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
LOGE(@"Preflight failed, will try again once %@ is reachable",
|
||||
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
|
||||
[self startReachability];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logUploadWithSyncState:(SNTCommandSyncState *)syncState {
|
||||
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Log upload complete");
|
||||
} else {
|
||||
LOGE(@"Log upload failed, continuing anyway");
|
||||
}
|
||||
return [self eventUploadWithSyncState:syncState];
|
||||
}
|
||||
|
||||
- (void)eventUploadWithSyncState:(SNTCommandSyncState *)syncState {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Event upload complete");
|
||||
return [self ruleDownloadWithSyncState:syncState];
|
||||
} else {
|
||||
LOGE(@"Event upload failed, aborting run");
|
||||
if (!syncState.daemon) exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)ruleDownloadWithSyncState:(SNTCommandSyncState *)syncState {
|
||||
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Rule download complete");
|
||||
if (syncState.bundleBinaryRequests.count) {
|
||||
return [self eventUploadBundleBinariesWithSyncState:syncState];
|
||||
}
|
||||
return [self postflightWithSyncState:syncState];
|
||||
} else {
|
||||
LOGE(@"Rule download failed, aborting run");
|
||||
if (!syncState.daemon) exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)eventUploadBundleBinariesWithSyncState:(SNTCommandSyncState *)syncState {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
|
||||
if ([p syncBundleEvents]) {
|
||||
LOGD(@"Event upload for bundle binaries complete");
|
||||
} else {
|
||||
LOGW(@"Event upload for bundle binary search failed");
|
||||
}
|
||||
return [self postflightWithSyncState:syncState];
|
||||
}
|
||||
|
||||
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
|
||||
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Postflight complete");
|
||||
LOGI(@"Sync completed successfully");
|
||||
if (!syncState.daemon) exit(0);
|
||||
} else {
|
||||
LOGE(@"Postflight failed");
|
||||
if (!syncState.daemon) exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark internal helpers
|
||||
|
||||
- (dispatch_source_t)createSyncTimerWithBlock:(void (^)())block {
|
||||
dispatch_source_t timerQueue = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
dispatch_source_set_event_handler(timerQueue, block);
|
||||
dispatch_resume(timerQueue);
|
||||
return timerQueue;
|
||||
}
|
||||
|
||||
- (SNTCommandSyncState *)createSyncState {
|
||||
// Gather some data needed during some sync stages
|
||||
SNTCommandSyncState *syncState = [[SNTCommandSyncState alloc] init];
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
syncState.syncBaseURL = config.syncBaseURL;
|
||||
if (syncState.syncBaseURL.absoluteString.length == 0) {
|
||||
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
|
||||
if (!syncState.daemon) exit(1);
|
||||
} else if (![syncState.syncBaseURL.scheme isEqual:@"https"]) {
|
||||
LOGW(@"SyncBaseURL is not over HTTPS!");
|
||||
}
|
||||
|
||||
syncState.machineID = config.machineID;
|
||||
if (syncState.machineID.length == 0) {
|
||||
LOGE(@"Missing Machine ID. Can't sync without it.");
|
||||
if (!syncState.daemon) exit(1);
|
||||
}
|
||||
|
||||
syncState.machineOwner = config.machineOwner;
|
||||
if (syncState.machineOwner.length == 0) {
|
||||
syncState.machineOwner = @"";
|
||||
LOGW(@"Missing Machine Owner.");
|
||||
}
|
||||
|
||||
[[self.daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
|
||||
syncState.xsrfToken = token;
|
||||
}];
|
||||
|
||||
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
|
||||
authURLSession.userAgent = @"santactl-sync/";
|
||||
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
if (santactlVersion) {
|
||||
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
|
||||
}
|
||||
authURLSession.refusesRedirects = YES;
|
||||
authURLSession.serverHostname = syncState.syncBaseURL.host;
|
||||
authURLSession.loggingBlock = ^(NSString *line) {
|
||||
LOGD(@"%@", line);
|
||||
};
|
||||
|
||||
// Configure server auth
|
||||
if ([config syncServerAuthRootsFile]) {
|
||||
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
|
||||
} else if ([config syncServerAuthRootsData]) {
|
||||
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
|
||||
}
|
||||
|
||||
// Configure client auth
|
||||
if ([config syncClientAuthCertificateFile]) {
|
||||
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
|
||||
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
|
||||
} else if ([config syncClientAuthCertificateCn]) {
|
||||
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
|
||||
} else if ([config syncClientAuthCertificateIssuer]) {
|
||||
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
|
||||
}
|
||||
|
||||
syncState.session = [authURLSession session];
|
||||
syncState.daemonConn = self.daemonConn;
|
||||
syncState.daemon = self.daemon;
|
||||
|
||||
return syncState;
|
||||
}
|
||||
|
||||
- (void)lockAction:(NSString *)action {
|
||||
[self.dispatchLock setObject:@YES forKey:action];
|
||||
}
|
||||
|
||||
- (void)unlockAction:(NSString *)action {
|
||||
[self.dispatchLock removeObjectForKey:action];
|
||||
}
|
||||
|
||||
- (BOOL)checkLockAction:(NSString *)action {
|
||||
return ([self.dispatchLock objectForKey:action] == nil);
|
||||
}
|
||||
|
||||
#pragma mark reachability methods
|
||||
|
||||
- (void)setReachable:(BOOL)reachable {
|
||||
_reachable = reachable;
|
||||
if (reachable) {
|
||||
[self stopReachability];
|
||||
[self fullSync];
|
||||
}
|
||||
}
|
||||
|
||||
// Start listening for network state changes on a background thread
|
||||
- (void)startReachability {
|
||||
if (_reachability) return;
|
||||
const char *nodename = [[SNTConfigurator configurator] syncBaseURL].absoluteString.UTF8String;
|
||||
_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, nodename);
|
||||
SCNetworkReachabilityContext context = {
|
||||
.info = (__bridge void *)self
|
||||
};
|
||||
if (SCNetworkReachabilitySetCallback(_reachability, reachabilityHandler, &context)) {
|
||||
SCNetworkReachabilitySetDispatchQueue(
|
||||
_reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
} else {
|
||||
[self stopReachability];
|
||||
}
|
||||
}
|
||||
|
||||
// Stop listening for network state changes
|
||||
- (void)stopReachability {
|
||||
if (_reachability) {
|
||||
SCNetworkReachabilitySetDispatchQueue(_reachability, NULL);
|
||||
CFRelease(_reachability);
|
||||
_reachability = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -37,20 +37,35 @@
|
||||
requestDict[kHostname] = [SNTSystemInfo longHostname];
|
||||
requestDict[kOSVer] = [SNTSystemInfo osVersion];
|
||||
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
|
||||
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
requestDict[kPrimaryUser] = self.syncState.machineOwner;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
|
||||
requestDict[kBinaryRuleCount] = @(binary);
|
||||
requestDict[kCertificateRuleCount] = @(certificate);
|
||||
dispatch_semaphore_signal(sema);
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case SNTClientModeMonitor:
|
||||
requestDict[kClientMode] = kClientModeMonitor; break;
|
||||
case SNTClientModeLockdown:
|
||||
requestDict[kClientMode] = kClientModeLockdown; break;
|
||||
default: break;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
|
||||
|
||||
// If user requested it or we've never had a successful sync, try from a clean slate.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] ||
|
||||
[[SNTConfigurator configurator] syncCleanRequired]) {
|
||||
LOGD(@"Clean sync requested by user");
|
||||
requestDict[kRequestCleanSync] = @YES;
|
||||
}
|
||||
|
||||
@@ -59,10 +74,9 @@
|
||||
|
||||
if (!resp) return NO;
|
||||
|
||||
self.syncState.eventBatchSize = [resp[kBatchSize] intValue];
|
||||
if (self.syncState.eventBatchSize == 0) {
|
||||
self.syncState.eventBatchSize = 50;
|
||||
}
|
||||
self.syncState.FCMToken = resp[kFCMToken];
|
||||
|
||||
self.syncState.eventBatchSize = [resp[kBatchSize] intValue] ?: 50;
|
||||
|
||||
self.syncState.uploadLogURL = [NSURL URLWithString:resp[kUploadLogsURL]];
|
||||
|
||||
@@ -81,6 +95,7 @@
|
||||
}
|
||||
|
||||
if ([resp[kCleanSync] boolValue]) {
|
||||
LOGD(@"Clean sync requested by server");
|
||||
self.syncState.cleanSync = YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,26 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
sema = dispatch_semaphore_create(0);
|
||||
[[self.daemonConn remoteObjectProxy] setRuleSyncLastSuccess:[NSDate date] reply:^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
|
||||
|
||||
LOGI(@"Added %lu rules", self.syncState.downloadedRules.count);
|
||||
|
||||
if (self.syncState.targetedRuleSync) {
|
||||
for (SNTRule *r in self.syncState.downloadedRules) {
|
||||
NSString *fileName = [[self.syncState.ruleSyncCache objectForKey:r.shasum] copy];
|
||||
[self.syncState.ruleSyncCache removeObjectForKey:r.shasum];
|
||||
if (fileName) {
|
||||
NSString *message = [NSString stringWithFormat:@"%@ can now be run", fileName];
|
||||
[[self.daemonConn remoteObjectProxy]
|
||||
postRuleSyncNotificationWithCustomMessage:message reply:^{}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
- (BOOL)sync;
|
||||
|
||||
/**
|
||||
The URL for this stage.
|
||||
|
||||
The URL for this stage.
|
||||
|
||||
@return The NSURL for this stage.
|
||||
*/
|
||||
- (nonnull NSURL *)stageURL;
|
||||
@@ -49,7 +49,7 @@
|
||||
/**
|
||||
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;
|
||||
@@ -58,7 +58,7 @@
|
||||
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.
|
||||
@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.
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
@property(readwrite) NSURLSession *urlSession;
|
||||
@property(readwrite) SNTCommandSyncState *syncState;
|
||||
@property(readwrite) SNTXPCConnection *daemonConn;
|
||||
@property BOOL xsrfFetched;
|
||||
|
||||
@end
|
||||
|
||||
@@ -170,9 +171,9 @@
|
||||
}
|
||||
|
||||
- (BOOL)fetchXSRFToken {
|
||||
__block BOOL success = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{ // only fetch token once per session
|
||||
BOOL success = NO;
|
||||
if (!self.xsrfFetched) { // only fetch token once per session
|
||||
self.xsrfFetched = YES;
|
||||
NSString *stageName = [@"xsrf" stringByAppendingFormat:@"/%@", self.syncState.machineID];
|
||||
NSURL *u = [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:u];
|
||||
@@ -188,7 +189,7 @@
|
||||
} else {
|
||||
LOGD(@"Failed to retrieve XSRF token");
|
||||
}
|
||||
});
|
||||
};
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
/// An XSRF token to send in the headers with each request.
|
||||
@property NSString *xsrfToken;
|
||||
|
||||
/// A FCM token to subscribe to push notifications.
|
||||
@property(copy) NSString *FCMToken;
|
||||
|
||||
/// Machine identifier and owner.
|
||||
@property(copy) NSString *machineID;
|
||||
@property(copy) NSString *machineOwner;
|
||||
@@ -56,4 +59,13 @@
|
||||
/// Rules downloaded from server.
|
||||
@property NSMutableArray *downloadedRules;
|
||||
|
||||
/// Returns YES if the santactl session is running as a daemon, returns NO otherwise.
|
||||
@property BOOL daemon;
|
||||
|
||||
/// Returns YES if the session is targeted for this machine, returns NO otherwise.
|
||||
@property BOOL targetedRuleSync;
|
||||
|
||||
/// Reference to the sync manager's ruleSyncCache. Used to lookup binary names for notifications.
|
||||
@property(weak) NSCache *ruleSyncCache;
|
||||
|
||||
@end
|
||||
|
||||
@@ -77,13 +77,7 @@ static NSMutableDictionary *registeredCommands;
|
||||
exit(1);
|
||||
};
|
||||
[daemonConn resume];
|
||||
} else {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[daemonConn resume];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return daemonConn;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,15 +44,6 @@
|
||||
///
|
||||
- (NSUInteger)pendingEventsCount;
|
||||
|
||||
///
|
||||
/// Retrieve an event from the database with a given SHA-256. If multiple events
|
||||
/// exist for the same SHA-256, just the first is returned.
|
||||
///
|
||||
/// @param sha256 a SHA-256 of the binary to return an event for.
|
||||
/// @return a single SNTStoredEvent.
|
||||
///
|
||||
- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256;
|
||||
|
||||
///
|
||||
/// Delete a single event from the database using its index.
|
||||
///
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#import "SNTEventTable.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTEventTable
|
||||
@@ -66,13 +65,26 @@
|
||||
newVersion = 2;
|
||||
}
|
||||
|
||||
if (version < 3) {
|
||||
// Clean-up: Disable AUTOINCREMENT on idx column
|
||||
[db executeUpdate:@"CREATE TABLE 'events_tmp' ("
|
||||
@"'idx' INTEGER PRIMARY KEY,"
|
||||
@"'filesha256' TEXT NOT NULL,"
|
||||
@"'eventdata' BLOB);"];
|
||||
[db executeUpdate:@"INSERT INTO events_tmp SELECT * FROM events"];
|
||||
[db executeUpdate:@"DROP TABLE events"];
|
||||
[db executeUpdate:@"ALTER TABLE events_tmp RENAME TO events"];
|
||||
newVersion = 3;
|
||||
}
|
||||
|
||||
return newVersion;
|
||||
}
|
||||
|
||||
#pragma mark Loading / Storing
|
||||
|
||||
- (BOOL)addStoredEvent:(SNTStoredEvent *)event {
|
||||
if (!event.fileSHA256 ||
|
||||
if (!event.idx ||
|
||||
!event.fileSHA256 ||
|
||||
!event.filePath ||
|
||||
!event.occurrenceDate ||
|
||||
!event.decision) return NO;
|
||||
@@ -86,8 +98,9 @@
|
||||
|
||||
__block BOOL success = NO;
|
||||
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
|
||||
success = [db executeUpdate:@"INSERT INTO 'events' (filesha256, eventdata) VALUES (?, ?)",
|
||||
event.fileSHA256, eventData];
|
||||
success = [db executeUpdate:@"INSERT INTO 'events' (idx, filesha256, eventdata)"
|
||||
@"VALUES (?, ?, ?)",
|
||||
event.idx, event.fileSHA256, eventData];
|
||||
}];
|
||||
|
||||
return success;
|
||||
@@ -103,26 +116,6 @@
|
||||
return eventsPending;
|
||||
}
|
||||
|
||||
- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256 {
|
||||
__block SNTStoredEvent *storedEvent;
|
||||
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
FMResultSet *rs =
|
||||
[db executeQuery:@"SELECT * FROM events WHERE filesha256=? LIMIT 1;", sha256];
|
||||
|
||||
if ([rs next]) {
|
||||
storedEvent = [self eventFromResultSet:rs];
|
||||
if (!storedEvent) {
|
||||
[db executeUpdate:@"DELETE FROM events WHERE idx=?", [rs objectForColumnName:@"idx"]];
|
||||
}
|
||||
}
|
||||
|
||||
[rs close];
|
||||
}];
|
||||
|
||||
return storedEvent;
|
||||
}
|
||||
|
||||
- (NSArray *)pendingEvents {
|
||||
NSMutableArray *pendingEvents = [[NSMutableArray alloc] init];
|
||||
|
||||
@@ -152,7 +145,7 @@
|
||||
|
||||
@try {
|
||||
event = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
|
||||
event.idx = @([rs intForColumn:@"idx"]);
|
||||
event.idx = event.idx ?: @((uint32_t)[rs intForColumn:@"idx"]);
|
||||
} @catch (NSException *exception) {
|
||||
}
|
||||
|
||||
|
||||
@@ -39,14 +39,11 @@
|
||||
- (NSUInteger)certificateRuleCount;
|
||||
|
||||
///
|
||||
/// @return Rule for binary with given SHA-256
|
||||
/// @return Rule for binary or certificate with given SHA-256. The binary rule will be returned
|
||||
/// if it exists. If not, the certificate rule will be returned if it exists.
|
||||
///
|
||||
- (SNTRule *)binaryRuleForSHA256:(NSString *)SHA256;
|
||||
|
||||
///
|
||||
/// @return Rule for certificate with given SHA-256
|
||||
///
|
||||
- (SNTRule *)certificateRuleForSHA256:(NSString *)SHA256;
|
||||
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256;
|
||||
|
||||
///
|
||||
/// Add an array of rules to the database. The rules will be added within a transaction and the
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
@"'type' INTEGER NOT NULL, "
|
||||
@"'custommsg' TEXT"
|
||||
@")"];
|
||||
[db executeUpdate:@"CREATE VIEW binrules AS SELECT * FROM rules WHERE type=1"];
|
||||
[db executeUpdate:@"CREATE VIEW certrules AS SELECT * FROM rules WHERE type=2"];
|
||||
[db executeUpdate:@"CREATE UNIQUE INDEX rulesunique ON rules (shasum, type)"];
|
||||
|
||||
[[SNTConfigurator configurator] setSyncCleanRequired:YES];
|
||||
@@ -49,6 +47,12 @@
|
||||
newVersion = 1;
|
||||
}
|
||||
|
||||
if (version < 2) {
|
||||
[db executeUpdate:@"DROP VIEW IF EXISTS binrules"];
|
||||
[db executeUpdate:@"DROP VIEW IF EXISTS certrules"];
|
||||
newVersion = 2;
|
||||
}
|
||||
|
||||
// Save hashes of the signing certs for launchd and santad.
|
||||
// Used to ensure rules for them are not removed.
|
||||
self.santadCertSHA = [[[[MOLCodesignChecker alloc] initWithSelf] leafCertificate] SHA256];
|
||||
@@ -57,11 +61,11 @@
|
||||
// Ensure the certificates used to sign the running launchd/santad are whitelisted.
|
||||
// If they weren't previously and the database is not new, log an error.
|
||||
int ruleCount = [db intForQuery:@"SELECT COUNT(*)"
|
||||
@"FROM certrules "
|
||||
@"WHERE (shasum=? OR shasum=?) AND state=?",
|
||||
@"FROM rules "
|
||||
@"WHERE (shasum=? OR shasum=?) AND state=? AND type=2",
|
||||
self.santadCertSHA, self.launchdCertSHA, @(SNTRuleStateWhitelist)];
|
||||
if (ruleCount != 2) {
|
||||
if (version > 0) LOGE(@"Started without launchd/santad certificate rules in place!");
|
||||
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 (?, ?, ?)",
|
||||
@@ -84,7 +88,7 @@
|
||||
- (NSUInteger)binaryRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM binrules"];
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=1"];
|
||||
}];
|
||||
return count;
|
||||
}
|
||||
@@ -92,7 +96,7 @@
|
||||
- (NSUInteger)certificateRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM certrules"];
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=2"];
|
||||
}];
|
||||
return count;
|
||||
}
|
||||
@@ -108,25 +112,15 @@
|
||||
return rule;
|
||||
}
|
||||
|
||||
- (SNTRule *)certificateRuleForSHA256:(NSString *)SHA256 {
|
||||
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256 {
|
||||
__block SNTRule *rule;
|
||||
|
||||
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
FMResultSet *rs = [db executeQuery:@"SELECT * FROM certrules WHERE shasum=? LIMIT 1", SHA256];
|
||||
if ([rs next]) {
|
||||
rule = [self ruleFromResultSet:rs];
|
||||
}
|
||||
[rs close];
|
||||
}];
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
- (SNTRule *)binaryRuleForSHA256:(NSString *)SHA256 {
|
||||
__block SNTRule *rule;
|
||||
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
FMResultSet *rs = [db executeQuery:@"SELECT * FROM binrules WHERE shasum=? LIMIT 1", SHA256];
|
||||
FMResultSet *rs =
|
||||
[db executeQuery:
|
||||
@"SELECT * FROM rules WHERE (shasum=? and type=1) OR (shasum=? AND type=2) LIMIT 1",
|
||||
binarySHA256, certificateSHA256];
|
||||
if ([rs next]) {
|
||||
rule = [self ruleFromResultSet:rs];
|
||||
}
|
||||
@@ -149,12 +143,12 @@
|
||||
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
|
||||
// Protect rules for santad/launchd certificates.
|
||||
NSPredicate *p = [NSPredicate predicateWithFormat:
|
||||
@"(SELF.shasum = %@ OR SELF.shasum = %@) AND SELF.type = %d",
|
||||
self.santadCertSHA, self.launchdCertSHA, SNTRuleTypeCertificate];
|
||||
@"(SELF.shasum = %@ OR SELF.shasum = %@) AND SELF.type = %d",
|
||||
self.santadCertSHA, self.launchdCertSHA, SNTRuleTypeCertificate];
|
||||
NSArray *requiredHashes = [rules filteredArrayUsingPredicate:p];
|
||||
p = [NSPredicate predicateWithFormat:@"SELF.state == %d", SNTRuleStateWhitelist];
|
||||
NSArray *requiredHashesWhitelist = [requiredHashes filteredArrayUsingPredicate:p];
|
||||
if ((cleanSlate && requiredHashesWhitelist.count != 2) ||
|
||||
if ((cleanSlate && requiredHashesWhitelist.count < 2) ||
|
||||
(requiredHashes.count != requiredHashesWhitelist.count)) {
|
||||
LOGE(@"Received request to remove whitelist for launchd/santad certificates.");
|
||||
[self fillError:error code:SNTRuleTableErrorMissingRequiredRule message:nil];
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#import "SNTDaemonControlController.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTEventLog.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTExecutionController.h"
|
||||
@@ -30,6 +31,7 @@
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTNotificationQueue.h"
|
||||
#import "SNTRuleTable.h"
|
||||
#import "SNTSyncdQueue.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@@ -68,11 +70,18 @@
|
||||
}
|
||||
|
||||
SNTNotificationQueue *notQueue = [[SNTNotificationQueue alloc] init];
|
||||
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
|
||||
|
||||
// Establish XPC listener for santactl connections
|
||||
// Restart santactl if it goes down
|
||||
syncdQueue.invalidationHandler = ^{
|
||||
[self startSyncd];
|
||||
};
|
||||
|
||||
// Establish XPC listener for Santa and santactl connections
|
||||
SNTDaemonControlController *dc = [[SNTDaemonControlController alloc] init];
|
||||
dc.driverManager = _driverManager;
|
||||
dc.notQueue = notQueue;
|
||||
dc.syncdQueue = syncdQueue;
|
||||
|
||||
_controlConnection =
|
||||
[[SNTXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]];
|
||||
@@ -81,6 +90,7 @@
|
||||
[_controlConnection resume];
|
||||
|
||||
__block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode];
|
||||
__block NSURL *origSyncURL = [[SNTConfigurator configurator] syncBaseURL];
|
||||
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
|
||||
handler:^(unsigned long data) {
|
||||
if (data & DISPATCH_VNODE_ATTRIB) {
|
||||
@@ -107,6 +117,14 @@
|
||||
[self.driverManager flushCache];
|
||||
}
|
||||
}
|
||||
|
||||
// Start santactl if the syncBaseURL changed from nil --> somthing
|
||||
NSURL *syncURL = [[SNTConfigurator configurator] syncBaseURL];
|
||||
if (!origSyncURL && syncURL) {
|
||||
origSyncURL = syncURL;
|
||||
LOGI(@"SyncBaseURL added, starting santactl.");
|
||||
[self startSyncd];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
@@ -117,13 +135,29 @@
|
||||
ruleTable:ruleTable
|
||||
eventTable:eventTable
|
||||
notifierQueue:notQueue
|
||||
syncdQueue:syncdQueue
|
||||
eventLog:_eventLog];
|
||||
// Start up santactl as a daemon if a sync server exists.
|
||||
[self startSyncd];
|
||||
|
||||
if (!_execController) return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)startSyncd {
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(EPERM);
|
||||
}
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
LOGI(@"Connected to driver, activating.");
|
||||
|
||||
@@ -135,26 +169,27 @@
|
||||
- (void)beginListeningForDecisionRequests {
|
||||
dispatch_queue_t exec_queue = dispatch_queue_create(
|
||||
"com.google.santad.execution_queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(exec_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||
dispatch_set_target_queue(
|
||||
exec_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||
|
||||
[self.driverManager listenForDecisionRequests:^(santa_message_t message) {
|
||||
@autoreleasepool {
|
||||
switch (message.action) {
|
||||
case ACTION_REQUEST_SHUTDOWN: {
|
||||
LOGI(@"Driver requested a shutdown");
|
||||
exit(0);
|
||||
dispatch_async(exec_queue, ^{
|
||||
switch (message.action) {
|
||||
case ACTION_REQUEST_SHUTDOWN: {
|
||||
LOGI(@"Driver requested a shutdown");
|
||||
exit(0);
|
||||
}
|
||||
case ACTION_REQUEST_BINARY: {
|
||||
[_execController validateBinaryWithMessage:message];
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOGE(@"Received decision request without a valid action: %d", message.action);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
case ACTION_REQUEST_BINARY: {
|
||||
dispatch_async(exec_queue, ^{
|
||||
[self.execController validateBinaryWithMessage:message];
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOGE(@"Received decision request without a valid action: %d", message.action);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -163,35 +198,38 @@
|
||||
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));
|
||||
|
||||
// Limit number of threads the queue can create.
|
||||
dispatch_semaphore_t concurrencyLimiter = dispatch_semaphore_create(15);
|
||||
|
||||
[self.driverManager listenForLogRequests:^(santa_message_t message) {
|
||||
@autoreleasepool {
|
||||
switch (message.action) {
|
||||
case ACTION_NOTIFY_DELETE:
|
||||
case ACTION_NOTIFY_EXCHANGE:
|
||||
case ACTION_NOTIFY_LINK:
|
||||
case ACTION_NOTIFY_RENAME:
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
dispatch_async(log_queue, ^{
|
||||
dispatch_semaphore_wait(concurrencyLimiter, DISPATCH_TIME_FOREVER);
|
||||
dispatch_async(log_queue, ^{
|
||||
switch (message.action) {
|
||||
case ACTION_NOTIFY_DELETE:
|
||||
case ACTION_NOTIFY_EXCHANGE:
|
||||
case ACTION_NOTIFY_LINK:
|
||||
case ACTION_NOTIFY_RENAME:
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] fileChangesRegex];
|
||||
NSString *path = @(message.path);
|
||||
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
|
||||
[self.eventLog logFileModification:message];
|
||||
[_eventLog logFileModification:message];
|
||||
}
|
||||
});
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXEC: {
|
||||
[_eventLog logAllowedExecution:message];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOGE(@"Received log request without a valid action: %d", message.action);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXEC: {
|
||||
dispatch_async(log_queue, ^{
|
||||
[self.eventLog logAllowedExecution:message];
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOGE(@"Received log request without a valid action: %d", message.action);
|
||||
break;
|
||||
}
|
||||
dispatch_semaphore_signal(concurrencyLimiter);
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
@class SNTDriverManager;
|
||||
@class SNTNotificationQueue;
|
||||
@class SNTSyncdQueue;
|
||||
|
||||
///
|
||||
/// SNTDaemonControlController handles all of the RPCs from santactl
|
||||
@@ -24,5 +25,6 @@
|
||||
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTNotificationQueue *notQueue;
|
||||
@property SNTSyncdQueue *syncdQueue;
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,17 +14,20 @@
|
||||
|
||||
#import "SNTDaemonControlController.h"
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTNotificationQueue.h"
|
||||
#import "SNTPolicyProcessor.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
#import "SNTSyncdQueue.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCNotifierInterface.h"
|
||||
#import "SNTXPCSyncdInterface.h"
|
||||
|
||||
// Globals used by the santad watchdog thread
|
||||
uint64_t watchdogCPUEvents = 0;
|
||||
@@ -34,7 +37,7 @@ double watchdogRAMPeak = 0;
|
||||
|
||||
@interface SNTDaemonControlController ()
|
||||
@property NSString *_syncXsrfToken;
|
||||
@property dispatch_source_t syncTimer;
|
||||
@property SNTPolicyProcessor *policyProcessor;
|
||||
@end
|
||||
|
||||
@implementation SNTDaemonControlController
|
||||
@@ -42,44 +45,12 @@ double watchdogRAMPeak = 0;
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_syncTimer = [self createSyncTimer];
|
||||
[self rescheduleSyncSecondsFromNow:30];
|
||||
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:
|
||||
[SNTDatabaseController ruleTable]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_source_t)createSyncTimer {
|
||||
dispatch_source_t syncTimerQ = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
|
||||
dispatch_source_set_event_handler(syncTimerQ, ^{
|
||||
[self rescheduleSyncSecondsFromNow:600];
|
||||
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[[SNTConfigurator configurator] setSyncBackOff:NO];
|
||||
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(EPERM);
|
||||
}
|
||||
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog", NULL));
|
||||
}
|
||||
});
|
||||
|
||||
dispatch_resume(syncTimerQ);
|
||||
|
||||
return syncTimerQ;
|
||||
}
|
||||
|
||||
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
|
||||
uint64_t interval = seconds * NSEC_PER_SEC;
|
||||
uint64_t leeway = (seconds * 0.05) * NSEC_PER_SEC;
|
||||
dispatch_source_set_timer(self.syncTimer, dispatch_walltime(NULL, interval), interval, leeway);
|
||||
}
|
||||
|
||||
#pragma mark Kernel ops
|
||||
|
||||
- (void)cacheCount:(void (^)(int64_t))reply {
|
||||
@@ -91,6 +62,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 {
|
||||
@@ -118,10 +93,6 @@ double watchdogRAMPeak = 0;
|
||||
reply([[SNTDatabaseController eventTable] pendingEventsCount]);
|
||||
}
|
||||
|
||||
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply {
|
||||
reply([[SNTDatabaseController eventTable] pendingEventForSHA256:sha256]);
|
||||
}
|
||||
|
||||
- (void)databaseEventsPending:(void (^)(NSArray *events))reply {
|
||||
reply([[SNTDatabaseController eventTable] pendingEvents]);
|
||||
}
|
||||
@@ -130,12 +101,22 @@ double watchdogRAMPeak = 0;
|
||||
[[SNTDatabaseController eventTable] deleteEventsWithIds:ids];
|
||||
}
|
||||
|
||||
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] binaryRuleForSHA256:sha256]);
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] ruleForBinarySHA256:binarySHA256
|
||||
certificateSHA256:certificateSHA256]);
|
||||
}
|
||||
|
||||
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] certificateRuleForSHA256:sha256]);
|
||||
#pragma mark Decision Ops
|
||||
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate
|
||||
reply:(void (^)(SNTEventState))reply {
|
||||
reply([self.policyProcessor decisionForFilePath:filePath
|
||||
fileSHA256:fileSHA256
|
||||
signingCertificate:signingCertificate].decision);
|
||||
}
|
||||
|
||||
#pragma mark Config Ops
|
||||
@@ -161,14 +142,13 @@ double watchdogRAMPeak = 0;
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
|
||||
[self rescheduleSyncSecondsFromNow:seconds];
|
||||
[[SNTConfigurator configurator] setSyncBackOff:YES];
|
||||
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
|
||||
[[SNTConfigurator configurator] setFullSyncLastSuccess:date];
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
|
||||
[[SNTConfigurator configurator] setSyncLastSuccess:date];
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
|
||||
[[SNTConfigurator configurator] setRuleSyncLastSuccess:date];
|
||||
reply();
|
||||
}
|
||||
|
||||
@@ -206,4 +186,41 @@ double watchdogRAMPeak = 0;
|
||||
self.notQueue.notifierConnection = c;
|
||||
}
|
||||
|
||||
#pragma mark syncd Ops
|
||||
|
||||
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener {
|
||||
// Only allow one active syncd connection
|
||||
if (self.syncdQueue.syncdConnection) return;
|
||||
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
|
||||
c.remoteInterface = [SNTXPCSyncdInterface syncdInterface];
|
||||
c.invalidationHandler = ^{
|
||||
[self.syncdQueue stopSyncingEvents];
|
||||
self.syncdQueue.syncdConnection = nil;
|
||||
self.syncdQueue.invalidationHandler();
|
||||
};
|
||||
c.acceptedHandler = ^{
|
||||
[self.syncdQueue startSyncingEvents];
|
||||
};
|
||||
[c resume];
|
||||
self.syncdQueue.syncdConnection = c;
|
||||
}
|
||||
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
|
||||
[[self.syncdQueue.syncdConnection remoteObjectProxy] rescheduleSyncSecondsFromNow:seconds];
|
||||
[[SNTConfigurator configurator] setSyncBackOff:YES];
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)pushNotifications:(void (^)(BOOL))reply {
|
||||
[self.syncdQueue.syncdConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
|
||||
reply(response);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply {
|
||||
[[self.notQueue.notifierConnection remoteObjectProxy]
|
||||
postRuleSyncNotificationWithCustomMessage:message];
|
||||
reply();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,42 +28,46 @@ static NSString *const kRulesDatabaseName = @"rules.db";
|
||||
static NSString *const kEventsDatabaseName = @"events.db";
|
||||
|
||||
+ (SNTEventTable *)eventTable {
|
||||
static FMDatabaseQueue *eventDatabaseQueue = nil;
|
||||
static SNTEventTable *eventDatabase;
|
||||
static dispatch_once_t eventDatabaseToken;
|
||||
dispatch_once(&eventDatabaseToken, ^{
|
||||
[self createDatabasePath];
|
||||
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kEventsDatabaseName];
|
||||
eventDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
FMDatabaseQueue *dbq = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
chown([fullPath UTF8String], 0, 0);
|
||||
chmod([fullPath UTF8String], 0600);
|
||||
|
||||
#ifndef DEBUG
|
||||
[eventDatabaseQueue inDatabase:^(FMDatabase *db) {
|
||||
[dbq inDatabase:^(FMDatabase *db) {
|
||||
db.logsErrors = NO;
|
||||
}];
|
||||
#endif
|
||||
|
||||
eventDatabase = [[SNTEventTable alloc] initWithDatabaseQueue:dbq];
|
||||
});
|
||||
|
||||
return [[SNTEventTable alloc] initWithDatabaseQueue:eventDatabaseQueue];
|
||||
return eventDatabase;
|
||||
}
|
||||
|
||||
+ (SNTRuleTable *)ruleTable {
|
||||
static FMDatabaseQueue *ruleDatabaseQueue = nil;
|
||||
static SNTRuleTable *ruleDatabase;
|
||||
static dispatch_once_t ruleDatabaseToken;
|
||||
dispatch_once(&ruleDatabaseToken, ^{
|
||||
[self createDatabasePath];
|
||||
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kRulesDatabaseName];
|
||||
ruleDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
FMDatabaseQueue *dbq = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
chown([fullPath UTF8String], 0, 0);
|
||||
chmod([fullPath UTF8String], 0600);
|
||||
|
||||
#ifndef DEBUG
|
||||
[ruleDatabaseQueue inDatabase:^(FMDatabase *db) {
|
||||
[dbq inDatabase:^(FMDatabase *db) {
|
||||
db.logsErrors = NO;
|
||||
}];
|
||||
#endif
|
||||
|
||||
ruleDatabase = [[SNTRuleTable alloc] initWithDatabaseQueue:dbq];
|
||||
});
|
||||
return [[SNTRuleTable alloc] initWithDatabaseQueue:ruleDatabaseQueue];
|
||||
return ruleDatabase;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
@@ -55,4 +55,9 @@
|
||||
///
|
||||
- (BOOL)flushCache;
|
||||
|
||||
///
|
||||
/// Check the kernel cache for a VnodeID
|
||||
///
|
||||
-(santa_action_t)checkCache:(uint64_t)vnodeID;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,12 +22,18 @@
|
||||
#import "MOLCertificate.h"
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
#import "SNTLogging.h"
|
||||
|
||||
@interface SNTEventLog ()
|
||||
@property NSMutableDictionary *detailStore;
|
||||
@property NSMutableDictionary<NSNumber *, SNTCachedDecision *> *detailStore;
|
||||
@property dispatch_queue_t detailStoreQueue;
|
||||
|
||||
// Caches for uid->username and gid->groupname lookups.
|
||||
@property NSCache<NSNumber *, NSString *> *userNameMap;
|
||||
@property NSCache<NSNumber *, NSString *> *groupNameMap;
|
||||
@end
|
||||
|
||||
@implementation SNTEventLog
|
||||
@@ -36,18 +42,27 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_detailStore = [NSMutableDictionary dictionaryWithCapacity:10000];
|
||||
_detailStoreQueue = dispatch_queue_create("com.google.santad.detail_store",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_userNameMap = [[NSCache alloc] init];
|
||||
_userNameMap.countLimit = 100;
|
||||
_groupNameMap = [[NSCache alloc] init];
|
||||
_groupNameMap.countLimit = 100;
|
||||
}
|
||||
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 +86,24 @@
|
||||
}
|
||||
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;
|
||||
struct passwd *pw = getpwuid(message.uid);
|
||||
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];
|
||||
}
|
||||
|
||||
[outStr appendFormat:@"|pid=%d|ppid=%d|process=%s|processpath=%s|uid=%d|user=%@|gid=%d|group=%@",
|
||||
message.pid, message.ppid, message.pname, ppath,
|
||||
message.uid, [self nameForUID:message.uid],
|
||||
message.gid, [self nameForGID:message.gid]];
|
||||
LOGI(@"%@", outStr);
|
||||
}
|
||||
|
||||
@@ -111,33 +112,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];
|
||||
r = @"CERT";
|
||||
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,39 +163,48 @@
|
||||
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;
|
||||
struct passwd *pw = getpwuid(message.uid);
|
||||
if (pw) user = @(pw->pw_name);
|
||||
struct group *gr = getgrgid(message.gid);
|
||||
if (gr) group = @(gr->gr_name);
|
||||
NSString *mode;
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor:
|
||||
mode = @"M"; break;
|
||||
case SNTClientModeLockdown:
|
||||
mode = @"L"; break;
|
||||
default:
|
||||
mode = @"U"; break;
|
||||
}
|
||||
|
||||
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=%@|mode=%@",
|
||||
message.pid, message.ppid,
|
||||
message.uid, [self nameForUID:message.uid],
|
||||
message.gid, [self nameForGID:message.gid],
|
||||
mode];
|
||||
|
||||
LOGI(@"%@", outLog);
|
||||
}
|
||||
@@ -234,70 +248,190 @@
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
/**
|
||||
Use sysctl to get the arguments for a PID, returned as a single string.
|
||||
Sanitize the given C-string, replacing |, \n and \r characters.
|
||||
|
||||
@return a new NSString with the replaced contents, if necessary, otherwise nil.
|
||||
*/
|
||||
- (NSString *)argsForPid:(pid_t)pid {
|
||||
int mib[3];
|
||||
- (NSString *)sanitizeCString:(const char *)str ofLength:(NSUInteger)length {
|
||||
NSUInteger bufOffset = 0, strOffset = 0;
|
||||
char c = 0;
|
||||
char *buf = NULL;
|
||||
BOOL shouldFree = NO;
|
||||
|
||||
// Get size of buffer required to store process arguments.
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_ARGMAX;
|
||||
int argmax;
|
||||
size_t size = sizeof(argmax);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) return nil;
|
||||
// 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);
|
||||
|
||||
// Create buffer to store args
|
||||
NSMutableData *argsdata = [NSMutableData dataWithCapacity:argmax];
|
||||
char *argsdatabytes = (char *)argsdata.mutableBytes;
|
||||
// Update the buffer and string offsets
|
||||
bufOffset += diff - strOffset;
|
||||
strOffset = diff + 1;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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=%@", @(&bytes[stringStart])];
|
||||
}
|
||||
|
||||
if (shouldFree) {
|
||||
free(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)nameForUID:(uid_t)uid {
|
||||
NSNumber *uidNumber = @(uid);
|
||||
|
||||
NSString *name = [self.userNameMap objectForKey:uidNumber];
|
||||
if (name) return name;
|
||||
|
||||
struct passwd *pw = getpwuid(uid);
|
||||
if (pw) {
|
||||
name = @(pw->pw_name);
|
||||
[self.userNameMap setObject:name forKey:uidNumber];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
- (NSString *)nameForGID:(gid_t)gid {
|
||||
NSNumber *gidNumber = @(gid);
|
||||
|
||||
NSString *name = [self.groupNameMap objectForKey:gidNumber];
|
||||
if (name) return name;
|
||||
|
||||
struct group *gr = getgrgid(gid);
|
||||
if (gr) {
|
||||
name = @(gr->gr_name);
|
||||
[self.groupNameMap setObject:name forKey:gidNumber];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,8 +443,7 @@
|
||||
- (NSString *)diskImageForDevice:(NSString *)devPath {
|
||||
devPath = [devPath stringByDeletingLastPathComponent];
|
||||
if (!devPath.length) return nil;
|
||||
io_registry_entry_t device = IORegistryEntryCopyFromPath(
|
||||
kIOMasterPortDefault, (__bridge CFStringRef)devPath);
|
||||
io_registry_entry_t device = IORegistryEntryFromPath(kIOMasterPortDefault, devPath.UTF8String);
|
||||
CFMutableDictionaryRef deviceProperties = NULL;
|
||||
IORegistryEntryCreateCFProperties(device, &deviceProperties, kCFAllocatorDefault, kNilOptions);
|
||||
NSDictionary *properties = CFBridgingRelease(deviceProperties);
|
||||
@@ -329,8 +462,7 @@
|
||||
- (NSString *)serialForDevice:(NSString *)devPath {
|
||||
if (!devPath.length) return nil;
|
||||
NSString *serial;
|
||||
io_registry_entry_t device = IORegistryEntryCopyFromPath(
|
||||
kIOMasterPortDefault, (__bridge CFStringRef)devPath);
|
||||
io_registry_entry_t device = IORegistryEntryFromPath(kIOMasterPortDefault, devPath.UTF8String);
|
||||
while (!serial && device) {
|
||||
CFMutableDictionaryRef deviceProperties = NULL;
|
||||
IORegistryEntryCreateCFProperties(device, &deviceProperties, kCFAllocatorDefault, kNilOptions);
|
||||
|
||||
@@ -21,12 +21,11 @@
|
||||
@class SNTEventTable;
|
||||
@class SNTNotificationQueue;
|
||||
@class SNTRuleTable;
|
||||
@class SNTSyncdQueue;
|
||||
|
||||
///
|
||||
/// SNTExecutionController is responsible for everything that happens when a request to execute
|
||||
/// a binary occurs:
|
||||
/// + Making a decision about whether to allow or deny this binary based on any existing rules
|
||||
/// for that specific binary, its signing certificate and the operating mode of santad.
|
||||
/// SNTExecutionController is responsible for handling binary execution requests:
|
||||
/// + Uses SNTPolicyProcessor to make a decision about whether to allow or deny the binary.
|
||||
/// + Sending the decision to the kernel as soon as possible
|
||||
/// + (If denied or unknown) Storing details about the execution event to the database
|
||||
/// for upload and spwaning santactl to quickly try and send that to the server.
|
||||
@@ -34,16 +33,11 @@
|
||||
///
|
||||
@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
|
||||
notifierQueue:(SNTNotificationQueue *)notifierQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue
|
||||
eventLog:(SNTEventLog *)eventLog;
|
||||
|
||||
///
|
||||
|
||||
@@ -32,9 +32,23 @@
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTNotificationQueue.h"
|
||||
#import "SNTPolicyProcessor.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "SNTSyncdQueue.h"
|
||||
|
||||
@interface SNTExecutionController ()
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTEventLog *eventLog;
|
||||
@property SNTEventTable *eventTable;
|
||||
@property SNTNotificationQueue *notifierQueue;
|
||||
@property SNTPolicyProcessor *policyProcessor;
|
||||
@property SNTRuleTable *ruleTable;
|
||||
@property SNTSyncdQueue *syncdQueue;
|
||||
|
||||
@property dispatch_queue_t eventQueue;
|
||||
@end
|
||||
|
||||
@implementation SNTExecutionController
|
||||
|
||||
@@ -44,6 +58,7 @@
|
||||
ruleTable:(SNTRuleTable *)ruleTable
|
||||
eventTable:(SNTEventTable *)eventTable
|
||||
notifierQueue:(SNTNotificationQueue *)notifierQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue
|
||||
eventLog:(SNTEventLog *)eventLog {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -51,7 +66,11 @@
|
||||
_ruleTable = ruleTable;
|
||||
_eventTable = eventTable;
|
||||
_notifierQueue = notifierQueue;
|
||||
_syncdQueue = syncdQueue;
|
||||
_eventLog = eventLog;
|
||||
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:_ruleTable];
|
||||
|
||||
_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.
|
||||
@@ -62,86 +81,52 @@
|
||||
|
||||
#pragma mark Binary Validation
|
||||
|
||||
- (SNTEventState)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
|
||||
SNTRule *rule = [self.ruleTable binaryRuleForSHA256:cd.sha256];
|
||||
if (rule) {
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowBinary;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockBinary;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
rule = [self.ruleTable certificateRuleForSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowCertificate;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockCertificate;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *msg = [self fileIsScopeBlacklisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return SNTEventStateBlockScope;
|
||||
}
|
||||
|
||||
msg = [self fileIsScopeWhitelisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return SNTEventStateAllowScope;
|
||||
}
|
||||
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor: return SNTEventStateAllowUnknown;
|
||||
case SNTClientModeLockdown: return SNTEventStateBlockUnknown;
|
||||
default: return SNTEventStateBlockUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)validateBinaryWithMessage:(santa_message_t)message {
|
||||
// Get info about the file. If we can't get this info, allow execution and log an error.
|
||||
if (unlikely(message.path == NULL)) {
|
||||
LOGE(@"Path for vnode_id is NULL: %llu", message.vnode_id);
|
||||
[_driverManager postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:message.vnode_id];
|
||||
return;
|
||||
}
|
||||
NSError *fileInfoError;
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
|
||||
if (!binInfo) {
|
||||
LOGW(@"Failed to read file %@: %@", binInfo.path, fileInfoError.localizedDescription);
|
||||
[self.driverManager postToKernelAction:ACTION_RESPOND_ALLOW
|
||||
forVnodeID:message.vnode_id];
|
||||
if (unlikely(!binInfo)) {
|
||||
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
|
||||
[_driverManager postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:message.vnode_id];
|
||||
return;
|
||||
}
|
||||
|
||||
// PrinterProxy workaround, see description above the method for more details.
|
||||
if ([self printerProxyWorkaround:binInfo]) return;
|
||||
if ([self printerProxyWorkaround:binInfo]) {
|
||||
[_driverManager postToKernelAction:ACTION_RESPOND_DENY forVnodeID:message.vnode_id];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get codesigning info about the file.
|
||||
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path];
|
||||
NSError *csError;
|
||||
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path
|
||||
error:&csError];
|
||||
// We specifically ignore CSInfoPlistFailed (-67030) as it sometimes appears spuriously
|
||||
// when trying to validate a binary separately from its bundle.
|
||||
if (csError && csError.code != errSecCSInfoPlistFailed) {
|
||||
csInfo = nil;
|
||||
}
|
||||
|
||||
// Actually make the decision.
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
|
||||
cd.sha256 = binInfo.SHA256;
|
||||
cd.certCommonName = csInfo.leafCertificate.commonName;
|
||||
cd.certSHA256 = csInfo.leafCertificate.SHA256;
|
||||
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo
|
||||
fileSHA256:nil
|
||||
signingCertificate:csInfo.leafCertificate];
|
||||
cd.vnodeId = message.vnode_id;
|
||||
cd.quarantineURL = binInfo.quarantineDataURL;
|
||||
cd.decision = [self makeDecision:cd binaryInfo:binInfo];
|
||||
|
||||
// Formulate an action from the decision
|
||||
santa_action_t action =
|
||||
(SNTEventStateAllow & cd.decision) ? ACTION_RESPOND_ALLOW : ACTION_RESPOND_DENY;
|
||||
|
||||
// Save decision details for logging the execution later.
|
||||
santa_action_t action = [self actionForEventState:cd.decision];
|
||||
if (action == ACTION_RESPOND_ALLOW) [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 &&
|
||||
@@ -183,86 +168,56 @@
|
||||
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) {
|
||||
// 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];
|
||||
NSMutableString *msg = [NSMutableString stringWithCapacity:1024];
|
||||
[msg appendFormat:@"\n\033[1mSanta\033[0m\n\n%@\n\n", s.string];
|
||||
[msg appendFormat:@"\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];
|
||||
[msg appendFormat:@"More info:\n%@\n\n", detailURL.absoluteString];
|
||||
}
|
||||
[self printMessage:msg toTTYForPID:message.ppid];
|
||||
|
||||
[self.notifierQueue addEvent:se customMessage:cd.customMsg];
|
||||
[_notifierQueue addEvent:se customMessage:cd.customMsg];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the file at @c path is in-scope for checking with Santa.
|
||||
///
|
||||
/// Files that are out of scope:
|
||||
/// + Non Mach-O files that are not part of an installer package.
|
||||
/// + Files in whitelisted path.
|
||||
///
|
||||
/// @return @c YES if file is in scope, @c NO otherwise.
|
||||
///
|
||||
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
|
||||
// Determine if file is within a whitelisted path
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Whitelist Regex";
|
||||
}
|
||||
|
||||
// If file is not a Mach-O file, we're not interested unless it's part of an install package.
|
||||
// TODO(rah): Consider adding an option to check all scripts.
|
||||
// TODO(rah): Consider adding an option to disable package script checks.
|
||||
if (!fi.isMachO && ![fi.path hasPrefix:@"/private/tmp/PKInstallSandbox."]) {
|
||||
return @"Not a Mach-O";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)fileIsScopeBlacklisted:(SNTFileInfo *)fi {
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] blacklistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Blacklist Regex";
|
||||
}
|
||||
|
||||
if ([[SNTConfigurator configurator] enablePageZeroProtection] && fi.isMissingPageZero) {
|
||||
return @"Missing __PAGEZERO";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Workaround for issue with PrinterProxy.app.
|
||||
|
||||
|
||||
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
|
||||
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.
|
||||
*/
|
||||
@@ -290,6 +245,13 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
/**
|
||||
This sends the event that was just saved to santactl for immediate upload, so that the user
|
||||
has something to vote in straight away.
|
||||
|
||||
This method is always called on a serial queue to keep this low-priority method
|
||||
away from the high-priority decision making threads.
|
||||
*/
|
||||
- (void)initiateEventUploadForEvent:(SNTStoredEvent *)event {
|
||||
// The event upload is skipped if the full path is equal to that of santactl so that
|
||||
// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
|
||||
@@ -298,45 +260,7 @@
|
||||
![[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.
|
||||
static dispatch_once_t onceToken;
|
||||
static NSMutableDictionary *uploadBackoff;
|
||||
dispatch_once(&onceToken, ^{
|
||||
uploadBackoff = [NSMutableDictionary dictionary];
|
||||
});
|
||||
NSDate *backoff = uploadBackoff[event.fileSHA256];
|
||||
NSDate *now = [NSDate date];
|
||||
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
|
||||
uploadBackoff[event.fileSHA256] = now;
|
||||
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(EPERM);
|
||||
}
|
||||
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog",
|
||||
"singleevent", [event.fileSHA256 UTF8String], NULL));
|
||||
}
|
||||
}
|
||||
|
||||
- (santa_action_t)actionForEventState:(SNTEventState)state {
|
||||
switch (state) {
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateAllowUnknown:
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
case SNTEventStateBlockBinary:
|
||||
case SNTEventStateBlockCertificate:
|
||||
case SNTEventStateBlockScope:
|
||||
case SNTEventStateBlockUnknown:
|
||||
return ACTION_RESPOND_DENY;
|
||||
default:
|
||||
LOGW(@"Invalid event state %ld", state);
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
[self.syncdQueue addEvent:event];
|
||||
}
|
||||
|
||||
- (void)printMessage:(NSString *)msg toTTYForPID:(pid_t)pid {
|
||||
@@ -347,44 +271,38 @@
|
||||
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 */ }
|
||||
// 16-bytes here is for future-proofing. Currently kern.tty.ptmx_max is
|
||||
// limited to 999 so 12 bytes should be enough.
|
||||
char devPath[16] = "/dev/";
|
||||
snprintf(devPath, 16, "/dev/%s", devname(taskInfo.e_tdev, S_IFCHR));
|
||||
int fd = open(devPath, O_WRONLY | O_NOCTTY);
|
||||
write(fd, msg.UTF8String, msg.length);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
|
||||
NSMutableDictionary *loggedInUsers = [[NSMutableDictionary alloc] init];
|
||||
NSMutableDictionary *loggedInHosts = [[NSMutableDictionary alloc] init];
|
||||
NSMutableSet *loggedInUsers = [NSMutableSet set];
|
||||
NSMutableArray *loggedInHosts = [NSMutableArray array];
|
||||
|
||||
struct utmpx *nxt;
|
||||
while ((nxt = getutxent())) {
|
||||
if (nxt->ut_type != USER_PROCESS) continue;
|
||||
|
||||
NSString *userName = @(nxt->ut_user);
|
||||
|
||||
NSString *sessionName;
|
||||
if (strnlen(nxt->ut_host, 1) > 0) {
|
||||
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_host];
|
||||
sessionName = [NSString stringWithFormat:@"%@@%s", userName, nxt->ut_host];
|
||||
} else {
|
||||
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_line];
|
||||
sessionName = [NSString stringWithFormat:@"%@@%s", userName, nxt->ut_line];
|
||||
}
|
||||
|
||||
if (userName.length > 0) {
|
||||
loggedInUsers[userName] = [NSNull null];
|
||||
}
|
||||
|
||||
if (sessionName.length > 1) {
|
||||
loggedInHosts[sessionName] = [NSNull null];
|
||||
}
|
||||
if (userName.length) [loggedInUsers addObject:userName];
|
||||
if (sessionName.length) [loggedInHosts addObject:sessionName];
|
||||
}
|
||||
|
||||
endutxent();
|
||||
|
||||
*users = [loggedInUsers allKeys];
|
||||
*sessions = [loggedInHosts allKeys];
|
||||
*users = [loggedInUsers allObjects];
|
||||
*sessions = [loggedInHosts copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
58
Source/santad/SNTPolicyProcessor.h
Normal file
58
Source/santad/SNTPolicyProcessor.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
#import <MOLCertificate.h>
|
||||
|
||||
@class SNTCachedDecision;
|
||||
@class SNTFileInfo;
|
||||
@class SNTRuleTable;
|
||||
|
||||
///
|
||||
/// Creates SNTCachedDecision objects from a SNTFileInfo object or a file path. Decisions are based
|
||||
/// on any existing rules for that specific binary, its signing certificate and the operating mode
|
||||
/// of santad.
|
||||
///
|
||||
@interface SNTPolicyProcessor : NSObject
|
||||
|
||||
///
|
||||
/// @param ruleTable The rule table to be used for every decision
|
||||
///
|
||||
- (nullable instancetype)initWithRuleTable:(nonnull SNTRuleTable *)ruleTable;
|
||||
|
||||
///
|
||||
/// @param fileInfo A SNTFileInfo object.
|
||||
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
|
||||
/// be calculated by this method from the filePath.
|
||||
/// @param signingCertificate A MOLCertificate object, can be nil.
|
||||
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
|
||||
/// returned. Binary rules take precedence over cert rules.
|
||||
///
|
||||
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
|
||||
fileSHA256:(nullable NSString *)fileSHA256
|
||||
signingCertificate:(nullable MOLCertificate *)signingCertificate;
|
||||
|
||||
///
|
||||
/// A wrapper for decisionForFileInfo:fileSHA256:signingCertificate:. This method is slower as it
|
||||
/// has to create the SNTFileInfo object. This is mainly used by the santactl binary because
|
||||
/// SNTFileInfo is not SecureCoding compliant. If the SHA256 hash of the file has already been
|
||||
/// calculated, use the fileSHA256 parameter to save a second calculation of the hash.
|
||||
///
|
||||
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
|
||||
fileSHA256:(nullable NSString *)fileSHA256
|
||||
signingCertificate:(nullable MOLCertificate *)signingCertificate;
|
||||
|
||||
@end
|
||||
164
Source/santad/SNTPolicyProcessor.m
Normal file
164
Source/santad/SNTPolicyProcessor.m
Normal file
@@ -0,0 +1,164 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTPolicyProcessor.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
|
||||
@interface SNTPolicyProcessor()
|
||||
@property SNTRuleTable *ruleTable;
|
||||
@end
|
||||
|
||||
@implementation SNTPolicyProcessor
|
||||
|
||||
- (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_ruleTable = ruleTable;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (SNTCachedDecision *)decisionForFileInfo:(SNTFileInfo *)fileInfo
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate {
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
|
||||
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
|
||||
cd.quarantineURL = fileInfo.quarantineDataURL;
|
||||
if (signingCertificate) {
|
||||
cd.certCommonName = signingCertificate.commonName;
|
||||
cd.certSHA256 = signingCertificate.SHA256;
|
||||
}
|
||||
|
||||
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256
|
||||
certificateSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.type) {
|
||||
case SNTRuleTypeBinary:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
cd.decision = SNTEventStateAllowBinary;
|
||||
return cd;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
cd.decision = SNTEventStateBlockBinary;
|
||||
return cd;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case SNTRuleTypeCertificate:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
cd.decision = SNTEventStateAllowCertificate;
|
||||
return cd;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
cd.decision = SNTEventStateBlockCertificate;
|
||||
return cd;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *msg = [self fileIsScopeBlacklisted:fileInfo];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
cd.decision = SNTEventStateBlockScope;
|
||||
return cd;
|
||||
}
|
||||
|
||||
msg = [self fileIsScopeWhitelisted:fileInfo];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
cd.decision = SNTEventStateAllowScope;
|
||||
return cd;
|
||||
}
|
||||
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor:
|
||||
cd.decision = SNTEventStateAllowUnknown;
|
||||
return cd;
|
||||
case SNTClientModeLockdown:
|
||||
cd.decision = SNTEventStateBlockUnknown;
|
||||
return cd;
|
||||
default:
|
||||
cd.decision = SNTEventStateBlockUnknown;
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTCachedDecision *)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate {
|
||||
NSError *error;
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
|
||||
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
|
||||
return [self decisionForFileInfo:fileInfo
|
||||
fileSHA256:fileSHA256
|
||||
signingCertificate:signingCertificate];
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the file at @c path is in-scope for checking with Santa.
|
||||
///
|
||||
/// Files that are out of scope:
|
||||
/// + Non Mach-O files that are not part of an installer package.
|
||||
/// + Files in whitelisted path.
|
||||
///
|
||||
/// @return @c YES if file is in scope, @c NO otherwise.
|
||||
///
|
||||
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
|
||||
// Determine if file is within a whitelisted path
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Whitelist Regex";
|
||||
}
|
||||
|
||||
// If file is not a Mach-O file, we're not interested unless it's part of an install package.
|
||||
// TODO(rah): Consider adding an option to check all scripts.
|
||||
// TODO(rah): Consider adding an option to disable package script checks.
|
||||
if (!fi.isMachO && ![fi.path hasPrefix:@"/private/tmp/PKInstallSandbox."]) {
|
||||
return @"Not a Mach-O";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)fileIsScopeBlacklisted:(SNTFileInfo *)fi {
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] blacklistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Blacklist Regex";
|
||||
}
|
||||
|
||||
if ([[SNTConfigurator configurator] enablePageZeroProtection] && fi.isMissingPageZero) {
|
||||
return @"Missing __PAGEZERO";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
@@ -12,20 +12,17 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SantaCachedDecision.h"
|
||||
@class SNTStoredEvent;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
OSDefineMetaClassAndStructors(SantaCachedDecision, OSObject);
|
||||
@interface SNTSyncdQueue : NSObject
|
||||
|
||||
uint64_t SantaCachedDecision::getMicrosecs() const {
|
||||
return microsecs_;
|
||||
}
|
||||
@property(nonatomic) SNTXPCConnection *syncdConnection;
|
||||
@property(copy) void (^invalidationHandler)();
|
||||
@property(copy) void (^acceptedHandler)();
|
||||
|
||||
santa_action_t SantaCachedDecision::getAction() const {
|
||||
return action_;
|
||||
}
|
||||
- (void)addEvent:(SNTStoredEvent *)event;
|
||||
- (void)startSyncingEvents;
|
||||
- (void)stopSyncingEvents;
|
||||
|
||||
void SantaCachedDecision::setAction(
|
||||
const santa_action_t action, const uint64_t microsecs) {
|
||||
action_ = action;
|
||||
microsecs_ = microsecs;
|
||||
}
|
||||
@end
|
||||
72
Source/santad/SNTSyncdQueue.m
Normal file
72
Source/santad/SNTSyncdQueue.m
Normal 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.
|
||||
|
||||
#import "SNTSyncdQueue.h"
|
||||
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCSyncdInterface.h"
|
||||
|
||||
@interface SNTSyncdQueue ()
|
||||
@property NSCache<NSString *, NSDate *> *uploadBackoff;
|
||||
@property dispatch_queue_t syncdQueue;
|
||||
@property dispatch_semaphore_t sema;
|
||||
@end
|
||||
|
||||
@implementation SNTSyncdQueue
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_uploadBackoff = [[NSCache alloc] init];
|
||||
_uploadBackoff.countLimit = 128;
|
||||
_syncdQueue = dispatch_queue_create("com.google.syncd_queue", DISPATCH_QUEUE_SERIAL);
|
||||
_sema = dispatch_semaphore_create(0);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addEvent:(SNTStoredEvent *)event {
|
||||
// The event upload is skipped if an event upload has been initiated for it in the
|
||||
// last 10 minutes.
|
||||
NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256];
|
||||
NSDate *now = [NSDate date];
|
||||
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
|
||||
[self.uploadBackoff setObject:now forKey:event.fileSHA256];
|
||||
|
||||
// Hold events for a few seconds to allow santad and santactl to establish connections.
|
||||
// If the connections are not established in time drop the event from the queue.
|
||||
// They will be uploaded during a full sync.
|
||||
dispatch_async(self.syncdQueue, ^{
|
||||
if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
|
||||
[self.syncdConnection.remoteObjectProxy postEventToSyncServer:event];
|
||||
|
||||
// Let em flow
|
||||
dispatch_semaphore_signal(self.sema);
|
||||
} else {
|
||||
LOGI(@"Dropping event %@ from com.google.syncd_queue", event.fileSHA256);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)startSyncingEvents {
|
||||
dispatch_semaphore_signal(self.sema);
|
||||
}
|
||||
|
||||
- (void)stopSyncingEvents {
|
||||
self.sema = dispatch_semaphore_create(0);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
@@ -136,43 +136,7 @@
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIFOzCCBCOgAwIBAgIIKtpxuqe9F58wDQYJKoZIhvcNAQEFBQAw
|
||||
fzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAk
|
||||
BgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMw
|
||||
MQYDVQQDDCpBcHBsZSBDb2RlIFNpZ25pbmcgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkwHhcNMTMwNDEyMjIzNDM1WhcNMjEwNDEyMjIz
|
||||
NDM1WjBWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5j
|
||||
LjEXMBUGA1UECwwOQXBwbGUgU29mdHdhcmUxGTAXBgNVBAMMEFNv
|
||||
ZnR3YXJlIFNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC/MLh0mE+uBguklG4xVG0J0TyjsDkQqdDmqmAiXdPk
|
||||
hKJAQZBkxmA9kWHaUqhFJ54sZMvkHqgkClI6s9PsFkF4wZ7RBuZ4
|
||||
JWMI89/KQeYd/jXpUVwTFYvp0Z1xe9HJqkuemdqPwCm4L5BvpLtl
|
||||
j4Bq1z1obeR4wqUSL/gy6X7JXVyMPhYgG9denRuGLQj3vBmkTQ5B
|
||||
pErbaxqARVAEqUyNFQfqaie9u4iePD+yUjmX47fI61RSmIovI1Zl
|
||||
5ekq2VG0I/oE3ffroN/VmvJeCPFfh/CxR2x1sbGM0RPjesHsYkF0
|
||||
poM08fladGQ5P1luzyzAYIMpPOfeT18N85M5XzCNAgMBAAGjggHi
|
||||
MIIB3jAdBgNVHQ4EFgQUxu0+Svsu6D8T1aAVs13Z57P3aDUwDAYD
|
||||
VR0TAQH/BAIwADAfBgNVHSMEGDAWgBSOaabEd0JOBKVWQpxRH4ba
|
||||
0iCPCTCCARwGA1UdIASCARMwggEPMIIBCwYJKoZIhvdjZAUBMIH9
|
||||
MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0
|
||||
aWZpY2F0ZWF1dGhvcml0eTCBwwYIKwYBBQUHAgIwgbYMgbNSZWxp
|
||||
YW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
|
||||
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
|
||||
ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2Us
|
||||
IGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBw
|
||||
cmFjdGljZSBzdGF0ZW1lbnRzLjA1BgNVHR8ELjAsMCqgKKAmhiRo
|
||||
dHRwOi8vY3JsLmFwcGxlLmNvbS9jb2Rlc2lnbmluZy5jcmwwDgYD
|
||||
VR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA8G
|
||||
CSqGSIb3Y2QGFgQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFfUxSFX
|
||||
GxTaEjIsEQUMBA+VqtTi+vLEbWjeUlINInPIhXMd71FO8IpJsGiU
|
||||
ZVEi3/1AjzW0aEBSuyWOzPrOfBJW2MDQVQW1SrG1YfyVfJFectEo
|
||||
tB0rbdpLZ58F/ObnWUpDXh97hDe+/rqKKzMFlFCDuP6a2wO7jWLy
|
||||
GW13k+N1zzZZMV4IbV0BHGVTUpN2eJwXCxAelLw2kFZLRC6Y2aYx
|
||||
ofAcZpSZVHMTtVE4vCSioDA7emWHrMC8FfReMMedoyoE38TPR4Rt
|
||||
n/3/RcOgGaw8u62PlPm5x8hxNhHt6AG6tHVIgqQqUxoFBZudxkcb
|
||||
9eggcqAbS+W+ZPw4DZr/Q0E=
|
||||
</data>
|
||||
<data>MIIFOzCCBCOgAwIBAgIIKtpxuqe9F58wDQYJKoZIhvcNAQEFBQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMwMQYDVQQDDCpBcHBsZSBDb2RlIFNpZ25pbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTMwNDEyMjIzNDM1WhcNMjEwNDEyMjIzNDM1WjBWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEXMBUGA1UECwwOQXBwbGUgU29mdHdhcmUxGTAXBgNVBAMMEFNvZnR3YXJlIFNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/MLh0mE+uBguklG4xVG0J0TyjsDkQqdDmqmAiXdPkhKJAQZBkxmA9kWHaUqhFJ54sZMvkHqgkClI6s9PsFkF4wZ7RBuZ4JWMI89/KQeYd/jXpUVwTFYvp0Z1xe9HJqkuemdqPwCm4L5BvpLtlj4Bq1z1obeR4wqUSL/gy6X7JXVyMPhYgG9denRuGLQj3vBmkTQ5BpErbaxqARVAEqUyNFQfqaie9u4iePD+yUjmX47fI61RSmIovI1Zl5ekq2VG0I/oE3ffroN/VmvJeCPFfh/CxR2x1sbGM0RPjesHsYkF0poM08fladGQ5P1luzyzAYIMpPOfeT18N85M5XzCNAgMBAAGjggHiMIIB3jAdBgNVHQ4EFgQUxu0+Svsu6D8T1aAVs13Z57P3aDUwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSOaabEd0JOBKVWQpxRH4ba0iCPCTCCARwGA1UdIASCARMwggEPMIIBCwYJKoZIhvdjZAUBMIH9MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eTCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwOi8vY3JsLmFwcGxlLmNvbS9jb2Rlc2lnbmluZy5jcmwwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA8GCSqGSIb3Y2QGFgQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFfUxSFXGxTaEjIsEQUMBA+VqtTi+vLEbWjeUlINInPIhXMd71FO8IpJsGiUZVEi3/1AjzW0aEBSuyWOzPrOfBJW2MDQVQW1SrG1YfyVfJFectEotB0rbdpLZ58F/ObnWUpDXh97hDe+/rqKKzMFlFCDuP6a2wO7jWLyGW13k+N1zzZZMV4IbV0BHGVTUpN2eJwXCxAelLw2kFZLRC6Y2aYxofAcZpSZVHMTtVE4vCSioDA7emWHrMC8FfReMMedoyoE38TPR4Rtn/3/RcOgGaw8u62PlPm5x8hxNhHt6AG6tHVIgqQqUxoFBZudxkcb9eggcqAbS+W+ZPw4DZr/Q0E=</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
@@ -212,35 +176,7 @@
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEDjCCAvagAwIBAgIBITANBgkqhkiG9w0BAQUFADBiMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
|
||||
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
|
||||
DUFwcGxlIFJvb3QgQ0EwHhcNMTExMDI0MTczOTQxWhcNMjYxMDI0
|
||||
MTczOTQxWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUg
|
||||
SW5jLjEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHkxMzAxBgNVBAMMKkFwcGxlIENvZGUgU2lnbmluZyBDZXJ0
|
||||
aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAKKoEXH/DvkLa/glDZiBXWtZvVobibPn5e7O
|
||||
OZgjNTlInyGrJ9nunCDwZDgIawynz9xQth0GxFvxXRqbVGWGcy9i
|
||||
5Ti9ARBkcm18aUdhnBAFJuPrhcIsJNxqwj+I/MysKUyhSXkRmnV2
|
||||
5R640NIJtExTePvfGHahj6SpMsqRp7b6l705qs0bUBGIq2rt62bK
|
||||
IEusOy3vqufWyYgtacKkKmEv24cC86EhuUyfDvj52S3KcgR/Ha5u
|
||||
+j+Is8yjQO4XhxhRlrzP5C2twulZTl0cZTMnA6pno5Mkh8eHeQK5
|
||||
XZizDu7NaQg+jEiSJLJt1zC+z9jkyKeXgdAeI9w4mV9h/oUCAwEA
|
||||
AaOBsTCBrjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYB
|
||||
BQUHAwMwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjmmmxHdC
|
||||
TgSlVkKcUR+G2tIgjwkwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40u
|
||||
QKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5h
|
||||
cHBsZS5jb20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUF
|
||||
AAOCAQEAcHOt9lIVarcVGN6pKtGddpsesmmWx8LD4SvQ7wddcPja
|
||||
PFpIR9s5bIDKc95iG7c6yqNaHuOH2iVKk5vvcxCTc13j9J1+3g+B
|
||||
9qmZwVhunPSJAL7PT/8C0w789fP0choysconDt6o05mPauaZ+2HJ
|
||||
T/IXsRhn8DDAxgruyESBpIm78XlBw+6uyGtnfMxsSYZMAtPTam4Y
|
||||
nPhcOMgwh5ow2mcouOKaedqfpTsfUWI7IvF+U3waC8PwTdxJRPKI
|
||||
iM46W7md6bK3W1KnxtVYiXK32MyzqBgdUJc/Hdpqrji/e3kxvmO5
|
||||
94WFF+ltisTiGJQv129SpZmx3USbB3CSiCZ32w==
|
||||
</data>
|
||||
<data>MIIEDjCCAvagAwIBAgIBITANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMTExMDI0MTczOTQxWhcNMjYxMDI0MTczOTQxWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMzAxBgNVBAMMKkFwcGxlIENvZGUgU2lnbmluZyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKKoEXH/DvkLa/glDZiBXWtZvVobibPn5e7OOZgjNTlInyGrJ9nunCDwZDgIawynz9xQth0GxFvxXRqbVGWGcy9i5Ti9ARBkcm18aUdhnBAFJuPrhcIsJNxqwj+I/MysKUyhSXkRmnV25R640NIJtExTePvfGHahj6SpMsqRp7b6l705qs0bUBGIq2rt62bKIEusOy3vqufWyYgtacKkKmEv24cC86EhuUyfDvj52S3KcgR/Ha5u+j+Is8yjQO4XhxhRlrzP5C2twulZTl0cZTMnA6pno5Mkh8eHeQK5XZizDu7NaQg+jEiSJLJt1zC+z9jkyKeXgdAeI9w4mV9h/oUCAwEAAaOBsTCBrjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjmmmxHdCTgSlVkKcUR+G2tIgjwkwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5hcHBsZS5jb20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAcHOt9lIVarcVGN6pKtGddpsesmmWx8LD4SvQ7wddcPjaPFpIR9s5bIDKc95iG7c6yqNaHuOH2iVKk5vvcxCTc13j9J1+3g+B9qmZwVhunPSJAL7PT/8C0w789fP0choysconDt6o05mPauaZ+2HJT/IXsRhn8DDAxgruyESBpIm78XlBw+6uyGtnfMxsSYZMAtPTam4YnPhcOMgwh5ow2mcouOKaedqfpTsfUWI7IvF+U3waC8PwTdxJRPKIiM46W7md6bK3W1KnxtVYiXK32MyzqBgdUJc/Hdpqrji/e3kxvmO594WFF+ltisTiGJQv129SpZmx3USbB3CSiCZ32w==</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
@@ -261,40 +197,7 @@
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
|
||||
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
|
||||
DUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5
|
||||
MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUg
|
||||
SW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmE
|
||||
Les2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRr
|
||||
EdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b
|
||||
8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6ws
|
||||
IG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
|
||||
C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CW
|
||||
QYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMU
|
||||
ZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
|
||||
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4w
|
||||
ggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggr
|
||||
BgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2Ev
|
||||
MIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2Vy
|
||||
dGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
|
||||
ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z
|
||||
IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9s
|
||||
aWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVu
|
||||
dHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J2
|
||||
0ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNii
|
||||
Pvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3
|
||||
iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
|
||||
1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP
|
||||
3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5
|
||||
MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJ
|
||||
BzewdXUh
|
||||
</data>
|
||||
<data>MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
@@ -314,7 +217,7 @@
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485894498.53763503</real>
|
||||
<real>485894498.537635</real>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
@@ -325,7 +228,7 @@
|
||||
<key>$classname</key>
|
||||
<string>NSDate</string>
|
||||
</dict>
|
||||
<integer>6</integer>
|
||||
<integer>131072</integer>
|
||||
<integer>11196</integer>
|
||||
<integer>10760</integer>
|
||||
<string>bash</string>
|
||||
@@ -454,7 +357,7 @@
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485894568.92822498</real>
|
||||
<real>485894568.928225</real>
|
||||
</dict>
|
||||
<integer>1</integer>
|
||||
<integer>11427</integer>
|
||||
|
||||
260
Tests/LogicTests/SNTCommandFileInfoTest.m
Normal file
260
Tests/LogicTests/SNTCommandFileInfoTest.m
Normal file
@@ -0,0 +1,260 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
@interface SNTCommandFileInfo : NSObject
|
||||
|
||||
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *);
|
||||
@property(nonatomic) NSMutableDictionary *propertyMap;
|
||||
+ (NSArray *)fileInfoKeys;
|
||||
+ (NSArray *)signingChainKeys;
|
||||
- (SNTAttributeBlock)codeSigned;
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath
|
||||
daemonConnection:(SNTXPCConnection *)daemonConn;
|
||||
+ (void)parseArguments:(NSArray *)args
|
||||
forKey:(NSString **)key
|
||||
certIndex:(NSNumber **)certIndex
|
||||
jsonOutput:(BOOL *)jsonOutput
|
||||
filePaths:(NSArray **)filePaths;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTCommandFileInfoTest : XCTestCase
|
||||
|
||||
@property SNTCommandFileInfo *cfi;
|
||||
@property id cscMock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTCommandFileInfoTest
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
|
||||
self.cfi = [[SNTCommandFileInfo alloc] initWithFilePath:nil daemonConnection:nil];
|
||||
self.cscMock = OCMClassMock([MOLCodesignChecker class]);
|
||||
OCMStub([self.cscMock alloc]).andReturn(self.cscMock);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
self.cfi = nil;
|
||||
[self.cscMock stopMocking];
|
||||
self.cscMock = nil;
|
||||
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsKey {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
[SNTCommandFileInfo parseArguments:@[ @"--key", @"SHA-256", @"/usr/bin/yes" ]
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertEqualObjects(key, @"SHA-256");
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsCertIndex {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
[SNTCommandFileInfo parseArguments:@[ @"--cert-index", @"1", @"/usr/bin/yes" ]
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertEqualObjects(certIndex, @(1));
|
||||
}
|
||||
- (void)testParseArgumentsJSON {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
[SNTCommandFileInfo parseArguments:@[ @"--json", @"/usr/bin/yes" ]
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertTrue(jsonOutput);
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsFilePaths {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
NSArray *args = @[ @"/usr/bin/yes", @"/bin/mv", @"--key", @"SHA-256", @"/bin/ls", @"--json",
|
||||
@"/bin/rm", @"--cert-index", @"1", @"/bin/cp"];
|
||||
[SNTCommandFileInfo parseArguments:args
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertEqual(filePaths.count, 5);
|
||||
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/mv"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/ls"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/rm"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/cp"]);
|
||||
}
|
||||
|
||||
- (void)testKeysAlignWithPropertyMap {
|
||||
NSArray *mapKeys = self.cfi.propertyMap.allKeys;
|
||||
NSArray *keys = [SNTCommandFileInfo fileInfoKeys];
|
||||
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
XCTAssertTrue([mapKeys containsObject:obj]);
|
||||
}];
|
||||
[mapKeys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
XCTAssertTrue([keys containsObject:obj]);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testCodeSignedNo {
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSUnsigned userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), @"No");
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureFailed {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedStaticCodeChanged {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSStaticCodeChanged userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureNotVerifiable {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureNotVerifiable userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureUnsupported {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureUnsupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceDirectoryFailed {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceDirectoryFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceNotSupported {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceNotSupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceRulesInvalid {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceRulesInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesInvalid {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesNotFound {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotFound userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesNotSealed {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotSealed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqFailed {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqInvalid {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqUnsupported {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqUnsupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedInfoPlistFailed {
|
||||
NSString *expected = @"Yes, but can't validate as Info.plist is missing";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSInfoPlistFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedDefault {
|
||||
NSString *expected = @"Yes, but failed to validate (999)";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:999 userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -39,10 +39,6 @@
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SNTCommandSyncEventUpload (Testing)
|
||||
- (NSArray *)findRelatedBinaries:(SNTStoredEvent *)event;
|
||||
@end
|
||||
|
||||
@interface SNTCommandSyncTest : XCTestCase
|
||||
@property SNTCommandSyncState *syncState;
|
||||
@property id<SNTDaemonControlXPC> daemonConnRop;
|
||||
@@ -253,41 +249,41 @@
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[@"events"];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertEqual(events.count, 2);
|
||||
|
||||
NSDictionary *event = events[0];
|
||||
XCTAssertEqualObjects(event[@"file_sha256"],
|
||||
XCTAssertEqualObjects(event[kFileSHA256],
|
||||
@"ff98fa0c0a1095fedcbe4d388a9760e71399a5c3c017a847ffa545663b57929a");
|
||||
XCTAssertEqualObjects(event[@"file_name"], @"yes");
|
||||
XCTAssertEqualObjects(event[@"file_path"], @"/usr/bin");
|
||||
XCTAssertEqualObjects(event[@"decision"], @"BLOCK_BINARY");
|
||||
XCTAssertEqualObjects(event[kFileName], @"yes");
|
||||
XCTAssertEqualObjects(event[kFilePath], @"/usr/bin");
|
||||
XCTAssertEqualObjects(event[kDecision], @"BLOCK_BINARY");
|
||||
NSArray *sessions = @[ @"foouser@console", @"foouser@ttys000"];
|
||||
XCTAssertEqualObjects(event[@"current_sessions"], sessions);
|
||||
XCTAssertEqualObjects(event[kCurrentSessions], sessions);
|
||||
NSArray *users = @[ @"foouser" ];
|
||||
XCTAssertEqualObjects(event[@"logged_in_users"], users);
|
||||
XCTAssertEqualObjects(event[@"executing_user"], @"root");
|
||||
XCTAssertEqualObjects(event[@"pid"], @(11196));
|
||||
XCTAssertEqualObjects(event[@"ppid"], @(10760));
|
||||
XCTAssertEqualObjects(event[@"execution_time"], @(1464201698.537635));
|
||||
XCTAssertEqualObjects(event[kLoggedInUsers], users);
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"root");
|
||||
XCTAssertEqualObjects(event[kPID], @(11196));
|
||||
XCTAssertEqualObjects(event[kPPID], @(10760));
|
||||
XCTAssertEqualObjects(event[kExecutionTime], @(1464201698.537635));
|
||||
|
||||
NSArray *certs = event[@"signing_chain"];
|
||||
NSArray *certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 3);
|
||||
|
||||
NSDictionary *cert = [certs firstObject];
|
||||
XCTAssertEqualObjects(cert[@"sha256"],
|
||||
XCTAssertEqualObjects(cert[kCertSHA256],
|
||||
@"2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32");
|
||||
XCTAssertEqualObjects(cert[@"cn"], @"Software Signing");
|
||||
XCTAssertEqualObjects(cert[@"org"], @"Apple Inc.");
|
||||
XCTAssertEqualObjects(cert[@"ou"], @"Apple Software");
|
||||
XCTAssertEqualObjects(cert[@"valid_from"], @(1365806075));
|
||||
XCTAssertEqualObjects(cert[@"valid_until"], @(1618266875));
|
||||
XCTAssertEqualObjects(cert[kCertCN], @"Software Signing");
|
||||
XCTAssertEqualObjects(cert[kCertOrg], @"Apple Inc.");
|
||||
XCTAssertEqualObjects(cert[kCertOU], @"Apple Software");
|
||||
XCTAssertEqualObjects(cert[kCertValidFrom], @(1365806075));
|
||||
XCTAssertEqualObjects(cert[kCertValidUntil], @(1618266875));
|
||||
|
||||
event = events[1];
|
||||
XCTAssertEqualObjects(event[@"file_name"], @"hub");
|
||||
XCTAssertEqualObjects(event[@"executing_user"], @"foouser");
|
||||
certs = event[@"signing_chain"];
|
||||
XCTAssertEqualObjects(event[kFileName], @"hub");
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"foouser");
|
||||
certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 0);
|
||||
|
||||
return YES;
|
||||
@@ -299,7 +295,6 @@
|
||||
- (void)testEventUploadBundleAndQuarantineData {
|
||||
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
sut = OCMPartialMock(sut);
|
||||
OCMStub([sut findRelatedBinaries:OCMOCK_ANY]);
|
||||
|
||||
NSData *eventData = [self dataFromFixture:@"sync_eventupload_input_quarantine.plist"];
|
||||
NSArray *events = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
|
||||
@@ -307,24 +302,24 @@
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[@"events"];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertEqual(events.count, 1);
|
||||
|
||||
NSDictionary *event = [events firstObject];
|
||||
XCTAssertEqualObjects(event[@"file_bundle_id"], @"com.luckymarmot.Paw");
|
||||
XCTAssertEqualObjects(event[@"file_bundle_path"], @"/Applications/Paw.app");
|
||||
XCTAssertEqualObjects(event[@"file_bundle_version"], @"2003004001");
|
||||
XCTAssertEqualObjects(event[@"file_bundle_version_string"], @"2.3.4");
|
||||
XCTAssertEqualObjects(event[@"quarantine_timestamp"], @(1464204868));
|
||||
XCTAssertEqualObjects(event[@"quarantine_agent_bundle_id"], @"com.google.Chrome");
|
||||
XCTAssertEqualObjects(event[@"quarantine_data_url"],
|
||||
XCTAssertEqualObjects(event[kFileBundleID], @"com.luckymarmot.Paw");
|
||||
XCTAssertEqualObjects(event[kFileBundlePath], @"/Applications/Paw.app");
|
||||
XCTAssertEqualObjects(event[kFileBundleVersion], @"2003004001");
|
||||
XCTAssertEqualObjects(event[kFileBundleShortVersionString], @"2.3.4");
|
||||
XCTAssertEqualObjects(event[kQuarantineTimestamp], @(1464204868));
|
||||
XCTAssertEqualObjects(event[kQuarantineAgentBundleID], @"com.google.Chrome");
|
||||
XCTAssertEqualObjects(event[kQuarantineDataURL],
|
||||
@"https://d3hevc2w7wq7nj.cloudfront.net/paw/Paw-2.3.4-2003004001.zip");
|
||||
XCTAssertEqualObjects(event[@"quarantine_referer_url"], @"https://luckymarmot.com/paw");
|
||||
XCTAssertEqualObjects(event[kQuarantineRefererURL], @"https://luckymarmot.com/paw");
|
||||
|
||||
return YES;
|
||||
}];
|
||||
|
||||
|
||||
[sut sync];
|
||||
}
|
||||
|
||||
@@ -332,13 +327,12 @@
|
||||
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
self.syncState.eventBatchSize = 1;
|
||||
sut = OCMPartialMock(sut);
|
||||
OCMStub([sut findRelatedBinaries:OCMOCK_ANY]);
|
||||
|
||||
NSData *eventData = [self dataFromFixture:@"sync_eventupload_input_basic.plist"];
|
||||
NSArray *events = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
|
||||
OCMStub([self.daemonConnRop databaseEventsPending:([OCMArg invokeBlockWithArgs:events, nil])]);
|
||||
|
||||
__block int requestCount;
|
||||
__block int requestCount = 0;
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
requestCount++;
|
||||
@@ -350,6 +344,28 @@
|
||||
XCTAssertEqual(requestCount, 2);
|
||||
}
|
||||
|
||||
- (void)testEventUploadBundleSearch {
|
||||
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
self.syncState.eventBatchSize = 128;
|
||||
self.syncState.bundleBinaryRequests = @[ @"/Applications/Safari.app"];
|
||||
sut = OCMPartialMock(sut);
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertGreaterThanOrEqual(events.count, 3);
|
||||
|
||||
for (NSDictionary *event in events) {
|
||||
XCTAssertEqualObjects(event[kDecision], kDecisionBundleBinary);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}];
|
||||
|
||||
[sut syncBundleEvents];
|
||||
}
|
||||
|
||||
#pragma mark - SNTCommandSyncRuleDownload Tests
|
||||
|
||||
- (void)testRuleDownload {
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:@"/usr/bin/false"];
|
||||
SNTStoredEvent *event;
|
||||
event = [[SNTStoredEvent alloc] init];
|
||||
event.idx = @(arc4random());
|
||||
event.filePath = @"/usr/bin/false";
|
||||
event.fileSHA256 = [binInfo SHA256];
|
||||
event.signingChain = [csInfo certificates];
|
||||
@@ -67,7 +68,7 @@
|
||||
SNTStoredEvent *event = [self createTestEvent];
|
||||
[self.sut addStoredEvent:event];
|
||||
|
||||
SNTStoredEvent *storedEvent = [self.sut pendingEventForSHA256:event.fileSHA256];
|
||||
SNTStoredEvent *storedEvent = [self.sut pendingEvents].firstObject;
|
||||
XCTAssertNotNil(storedEvent);
|
||||
XCTAssertEqualObjects(event.filePath, storedEvent.filePath);
|
||||
XCTAssertEqualObjects(event.signingChain, storedEvent.signingChain);
|
||||
@@ -81,8 +82,7 @@
|
||||
[self.sut addStoredEvent:newEvent];
|
||||
XCTAssertEqual(self.sut.pendingEventsCount, 1);
|
||||
|
||||
SNTStoredEvent *storedEvent = [self.sut pendingEventForSHA256:newEvent.fileSHA256];
|
||||
[self.sut deleteEventWithId:storedEvent.idx];
|
||||
[self.sut deleteEventWithId:newEvent.idx];
|
||||
XCTAssertEqual(self.sut.pendingEventsCount, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
self.mockCodesignChecker = OCMClassMock([MOLCodesignChecker class]);
|
||||
OCMStub([self.mockCodesignChecker alloc]).andReturn(self.mockCodesignChecker);
|
||||
OCMStub([self.mockCodesignChecker initWithBinaryPath:OCMOCK_ANY])
|
||||
OCMStub([self.mockCodesignChecker initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:NULL]])
|
||||
.andReturn(self.mockCodesignChecker);
|
||||
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
@@ -66,6 +66,7 @@
|
||||
ruleTable:self.mockRuleDatabase
|
||||
eventTable:self.mockEventDatabase
|
||||
notifierQueue:nil
|
||||
syncdQueue:nil
|
||||
eventLog:nil];
|
||||
}
|
||||
|
||||
@@ -95,7 +96,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateWhitelist;
|
||||
OCMStub([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeBinary;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -109,7 +111,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateBlacklist;
|
||||
OCMStub([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeBinary;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -119,7 +122,6 @@
|
||||
|
||||
- (void)testCertificateWhitelistRule {
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
id cert = OCMClassMock([MOLCertificate class]);
|
||||
OCMStub([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
@@ -127,7 +129,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateWhitelist;
|
||||
OCMStub([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeCertificate;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -137,7 +140,6 @@
|
||||
|
||||
- (void)testCertificateBlacklistRule {
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
id cert = OCMClassMock([MOLCertificate class]);
|
||||
OCMStub([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
@@ -145,7 +147,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateBlacklist;
|
||||
OCMStub([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeCertificate;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
|
||||
@@ -137,12 +137,12 @@
|
||||
cleanSlate:NO
|
||||
error:nil];
|
||||
|
||||
SNTRule *r = [self.sut binaryRuleForSHA256:@"a"];
|
||||
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.shasum, @"a");
|
||||
XCTAssertEqual(r.type, SNTRuleTypeBinary);
|
||||
|
||||
r = [self.sut binaryRuleForSHA256:@"b"];
|
||||
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil];
|
||||
XCTAssertNil(r);
|
||||
}
|
||||
|
||||
@@ -151,12 +151,12 @@
|
||||
cleanSlate:NO
|
||||
error:nil];
|
||||
|
||||
SNTRule *r = [self.sut certificateRuleForSHA256:@"b"];
|
||||
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b"];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.shasum, @"b");
|
||||
XCTAssertEqual(r.type, SNTRuleTypeCertificate);
|
||||
|
||||
r = [self.sut certificateRuleForSHA256:@"a"];
|
||||
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a"];
|
||||
XCTAssertNil(r);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
XCTAssertNotNil(sut);
|
||||
|
||||
OCMVerifyAll(mockConnection);
|
||||
[mockConnection stopMocking];
|
||||
}
|
||||
|
||||
- (void)testInitServer {
|
||||
@@ -52,6 +53,7 @@
|
||||
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"];
|
||||
XCTAssertNotNil(sut);
|
||||
OCMVerifyAll(mockListener);
|
||||
[mockListener stopMocking];
|
||||
}
|
||||
|
||||
- (void)testConnectionRejection {
|
||||
@@ -75,6 +77,8 @@
|
||||
[sutClient resume];
|
||||
|
||||
[self waitForExpectationsWithTimeout:3.0 handler:NULL];
|
||||
|
||||
[mockCodesignChecker stopMocking];
|
||||
}
|
||||
|
||||
- (void)testConnectionAcceptance {
|
||||
|
||||
153
Tests/LogicTests/SantaCacheTest.mm
Normal file
153
Tests/LogicTests/SantaCacheTest.mm
Normal file
@@ -0,0 +1,153 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "SantaCache.h"
|
||||
|
||||
@interface SantaCacheTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SantaCacheTest
|
||||
|
||||
- (void)setUp {
|
||||
self.continueAfterFailure = NO;
|
||||
}
|
||||
|
||||
- (void)testSetAndGet {
|
||||
auto sut = new SantaCache<uint64_t>();
|
||||
|
||||
sut->set(72057611258548992llu, 10000192);
|
||||
XCTAssertEqual(sut->get(72057611258548992llu), 10000192);
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testCacheRemove {
|
||||
auto sut = new SantaCache<uint64_t>();
|
||||
|
||||
sut->set(0xDEADBEEF, 42);
|
||||
sut->remove(0xDEADBEEF);
|
||||
|
||||
XCTAssertEqual(sut->get(0xDEADBEEF), 0);
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testBucketGrowCopy {
|
||||
auto sut = new SantaCache<uint64_t>();
|
||||
|
||||
sut->set(386, 42);
|
||||
sut->set(2434, 42);
|
||||
|
||||
XCTAssertEqual(sut->get(386), 42);
|
||||
XCTAssertEqual(sut->get(2434), 42);
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testBucketShrinkCopy {
|
||||
auto sut = new SantaCache<uint64_t>(100, 1);
|
||||
|
||||
sut->set(386, 42);
|
||||
sut->set(2434, 42);
|
||||
sut->set(4482, 42);
|
||||
|
||||
sut->remove(2434);
|
||||
|
||||
XCTAssertEqual(sut->get(386), 42);
|
||||
XCTAssertEqual(sut->get(2434), 0);
|
||||
XCTAssertEqual(sut->get(4482), 42);
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testCacheResetAtLimit {
|
||||
auto sut = new SantaCache<uint64_t>(5);
|
||||
|
||||
sut->set(1, 42);
|
||||
sut->set(2, 42);
|
||||
sut->set(3, 42);
|
||||
sut->set(4, 42);
|
||||
sut->set(5, 42);
|
||||
XCTAssertEqual(sut->get(3), 42);
|
||||
sut->set(6, 42);
|
||||
XCTAssertEqual(sut->get(3), 0);
|
||||
XCTAssertEqual(sut->get(6), 42);
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testThreading {
|
||||
auto sut = new SantaCache<uint64_t>();
|
||||
|
||||
for (int x = 0; x < 200; ++x) {
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
for (int i = 0; i < 5000; ++i) sut->set(i, 10000-i);
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
for (int i = 5000; i < 10000; ++i) sut->set(i, 10000-i);
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
XCTFail("Timed out while setting values for test");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10000; ++i) XCTAssertEqual(sut->get(i), 10000 - i);
|
||||
}
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testCount {
|
||||
auto sut = new SantaCache<uint64_t>();
|
||||
|
||||
XCTAssertEqual(sut->count(), 0);
|
||||
|
||||
sut->set(4012, 42);
|
||||
sut->set(42, 0);
|
||||
sut->set(0x8BADF00D, 40010);
|
||||
|
||||
XCTAssertEqual(sut->count(), 2);
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testStrings {
|
||||
auto sut = new SantaCache<std::string>();
|
||||
|
||||
sut->set(1, "deadbeef");
|
||||
sut->set(2, "feedface");
|
||||
|
||||
XCTAssertEqual(sut->count(), 2);
|
||||
XCTAssertEqual(sut->get(1), "deadbeef");
|
||||
XCTAssertEqual(sut->get(2), "feedface");
|
||||
|
||||
sut->remove(2);
|
||||
|
||||
XCTAssertTrue(sut->get(2).empty());
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user