Compare commits

...

8 Commits

Author SHA1 Message Date
Tom Burgin
d4ba4b082f codesign check: verify all architectures (#239)
* fileinfo rule: don't use certs that have codesigning errors

* pods: MOLCodesignChecker --> 1.8
2018-02-22 14:41:47 -05:00
Tom Burgin
cce43829eb use MOLFCMClient v1.5 (#238) 2018-02-16 14:35:52 -05:00
johnl
c1bfbac2fe Various small fixes to README.md (#237)
* Various small fixes to README.md

* Apply changes
2018-02-13 11:06:28 -05:00
Tom Burgin
fc87cde668 config: use KVO (#234)
* config: atomically update config

* config: add an explanation for sleep usage

* config: use mobileconfig in the getters

* config: cleanup file watcher

* config: spell

* config: clear or reload sync state on sync base url change

* config: Use KVO and Dependent Keys

* config: remove debug log

* config: review updates

* config: update rule sync getter and setter names

* config: get logical
2018-02-07 13:59:00 -05:00
Tom Burgin
400c413029 config: add option to disable mode change notifications (#235)
* config: add option to disable mode change notifications

* config: don't do extra work

* config: handle none, default and custom

* config: cleaner
2018-02-02 12:01:51 -05:00
Tom Burgin
0e6eb45732 santa-driver: add an acknowledge feature to allow timeouts (#220)
* santa-driver: Add an acknowledge feature to allow timeouts for lost requests

* project: cocoapods 1.3.1 update

* review updates
2018-01-26 11:33:54 -05:00
Tom Burgin
7ca2028c19 santabs: don't try to lookup nil bundle paths (#233) 2018-01-26 11:33:04 -05:00
Tom Burgin
08144b54a7 docs: updated configuration details (#232)
* docs: updated configuration details

* config: add example mobileconfig
2018-01-24 21:07:48 -05:00
27 changed files with 857 additions and 451 deletions

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadContent</key>
<dict>
<key>com.google.santa</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>BannedBlockMessage</key>
<string>This application has been banned</string>
<key>ClientMode</key>
<integer>1</integer>
<key>EnablePageZeroProtection</key>
<false/>
<key>EventDetailText</key>
<string>Open sync server</string>
<key>EventDetailURL</key>
<string>https://sync-server-hostname/blockables/%file_sha%</string>
<key>FileChangesRegex</key>
<string>^/(?!(?:private/tmp|Library/(?:Caches|Managed Installs/Logs|(?:Managed )?Preferences))/)</string>
<key>MachineIDKey</key>
<string>MachineUUID</string>
<key>MachineIDPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>MachineOwnerKey</key>
<string>Owner</string>
<key>MachineOwnerPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>ModeNotificationLockdown</key>
<string>Entering Lockdown mode</string>
<key>ModeNotificationMonitor</key>
<string>Entering Monitor mode&lt;br/&gt;Please be careful!</string>
<key>MoreInfoURL</key>
<string>https://sync-server-hostname/moreinfo</string>
<key>SyncBaseURL</key>
<string>https://sync-server-hostname/api/santa/</string>
<key>UnknownBlockMessage</key>
<string>This application has been blocked from executing.</string>
</dict>
</dict>
</array>
</dict>
</dict>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>com.google.santa</string>
<key>PayloadDisplayName</key>
<string>com.google.santa</string>
<key>PayloadIdentifier</key>
<string>com.google.santa</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>9020fb2d-cab3-420f-9268-acca4868bdd0</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -1,8 +1,12 @@
# Important
Santa v0.9.21 has moved to using an Apple [Configuration Profile](https://developer.apple.com/library/content/featuredarticles/iPhoneConfigurationProfileRef/Introduction/Introduction.html) to manage the local configuration. The old config file (`/var/db/santa/config.plist`) is no longer used.
# Configuration
Two configuration methods can be used to control Santa: local configuration and a sync server controlled configuration. There are certain options that can only be controlled with a local configuration and others that can only be controlled with a sync server controlled configuration. Additionally, there are options that can be controlled by both.
Two configuration methods can be used to control Santa: a local configuration profile and a sync server controlled configuration. There are certain options that can only be controlled with a local configuration profile and others that can only be controlled with a sync server controlled configuration. Additionally, there are options that can be controlled by both.
## Local Configuration
## Local Configuration Profile
| Key | Value Type | Description |
| ----------------------------- | ---------- | ---------------------------------------- |
@@ -32,7 +36,7 @@ Two configuration methods can be used to control Santa: local configuration and
| MachineIDPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
| MachineIDKey | String | The key to use on MachineIDPlist. |
*protected keys: If a sync server is configured, this setting cannot be changed while santad is running as it is assumed the setting will be provided by the sync server.
*overridable by the sync server: run `santactl status` to check the current running config
##### EventDetailURL
@@ -50,49 +54,108 @@ This property contains a kind of format string to be turned into the URL to send
For example: `https://sync-server-hostname/%machine_id%/%file_sha%`
##### Example Config
##### Example Configuration Profile
Here is an example of a configuration that could be set.
Here is an example of a configuration profile that could be set. It was generated with Tim Sutton's great [mcxToProfile](https://github.com/timsutton/mcxToProfile) tool. A copy is also available [here](com.google.santa.example.mobileconfig).
A few key points to when creating your configuration profile:
* `com.google.santa` needs to be the key inside `PayloadContent`
* The `PayloadScope` needs to be `System`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BannedBlockMessage</key>
<string>This application has been banned</string>
<key>ClientMode</key>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadContent</key>
<dict>
<key>com.google.santa</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>BannedBlockMessage</key>
<string>This application has been banned</string>
<key>ClientMode</key>
<integer>1</integer>
<key>EnablePageZeroProtection</key>
<false/>
<key>EventDetailText</key>
<string>Open sync server</string>
<key>EventDetailURL</key>
<string>https://sync-server-hostname/blockables/%file_sha%</string>
<key>FileChangesRegex</key>
<string>^/(?!(?:private/tmp|Library/(?:Caches|Managed Installs/Logs|(?:Managed )?Preferences))/)</string>
<key>MachineIDKey</key>
<string>MachineUUID</string>
<key>MachineIDPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>MachineOwnerKey</key>
<string>Owner</string>
<key>MachineOwnerPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>ModeNotificationLockdown</key>
<string>Entering Lockdown mode</string>
<key>ModeNotificationMonitor</key>
<string>Entering Monitor mode&lt;br/&gt;Please be careful!</string>
<key>MoreInfoURL</key>
<string>https://sync-server-hostname/moreinfo</string>
<key>SyncBaseURL</key>
<string>https://sync-server-hostname/api/santa/</string>
<key>UnknownBlockMessage</key>
<string>This application has been blocked from executing.</string>
</dict>
</dict>
</array>
</dict>
</dict>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>com.google.santa</string>
<key>PayloadDisplayName</key>
<string>com.google.santa</string>
<key>PayloadIdentifier</key>
<string>com.google.santa</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>9020fb2d-cab3-420f-9268-acca4868bdd0</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>EnablePageZeroProtection</key>
<false/>
<key>EventDetailText</key>
<string>Open sync server</string>
<key>EventDetailURL</key>
<string>https://sync-server-hostname/blockables/%file_sha%</string>
<key>FileChangesRegex</key>
<string>^/(?!(?:private/tmp|Library/(?:Caches|Managed Installs/Logs|(?:Managed )?Preferences))/)</string>
<key>MachineIDKey</key>
<string>MachineUUID</string>
<key>MachineIDPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>MachineOwnerKey</key>
<string>Owner</string>
<key>MachineOwnerPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>ModeNotificationLockdown</key>
<string>Entering Lockdown mode</string>
<key>ModeNotificationMonitor</key>
<string>Entering Monitor mode&lt;br/&gt;Please be careful!</string>
<key>MoreInfoURL</key>
<string>https://sync-server-hostname/moreinfo</string>
<key>SyncBaseURL</key>
<string>https://sync-server-hostname/api/santa/</string>
<key>UnknownBlockMessage</key>
<string>This application has been blocked from executing.</string>
</dict>
</plist>
```
Configuration profiles have a `.mobileconfig` file extension. There are many ways to install configuration profiles:
* Double click them in Finder
* Use the `/usr/bin/profiles` tool
* Use an MDM
## Sync server Provided Configuration
| Key | Value Type | Description |

View File

@@ -35,28 +35,19 @@ Running Santa in Lockdown Mode will stop all blacklisted binaries and additional
##### Changing Modes
There are two ways to change the running mode: changing the config.plist and with a sync server configuration.
There are two ways to change the running mode: changing the configuration profile and with a sync server configuration.
###### Change modes with the config.plist
###### Change modes with the configuration profile
The `ClientMode` config key is protected while santad is running and will revert any attempt to change it.
Set the `ClientMode` in your configuration profile to the integer value `1` for MONITOR or `2` for LOCKDOWN.
Change to Monitor mode:
```sh
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist
sudo defaults write /var/db/santa/config.plist ClientMode -int 1
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
```xml
<key>ClientMode</key>
<integer>1</integer>
```
Change to Lockdown mode:
Install your new configuration profile, it will overwrite any old `com.google.santa` profiles you may have already install. See the [configuration](../deployment/configuration.md) document for more details.
```sh
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist
sudo defaults write /var/db/santa/config.plist ClientMode -int 2
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
```
######Change modes with a sync server
###### Change modes with a sync server
The mode is set in the preflight sync stage. Use the key `client_mode` and a value of `MONITOR` or `LOCKDOWN`.

View File

@@ -4,10 +4,10 @@ PODS:
- FMDB/standard (2.6.2)
- MOLAuthenticatingURLSession (2.2):
- MOLCertificate (~> 1.5)
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- MOLFCMClient (1.3):
- MOLCertificate (1.7)
- MOLCodesignChecker (1.8):
- MOLCertificate (~> 1.7)
- MOLFCMClient (1.5):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.4)
@@ -22,11 +22,11 @@ DEPENDENCIES:
SPEC CHECKSUMS:
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
MOLCertificate: 1cdb264405631b4bbdcf4cde7627469290cf1187
MOLCodesignChecker: 93460b82eb41b671c1c8eff9fc904d0d3d149e16
MOLFCMClient: 88debb79f8c0454c3dd4f6514c2453e57a963c08
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe
COCOAPODS: 1.2.1
COCOAPODS: 1.4.0

View File

@@ -15,7 +15,7 @@ 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
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
Santa is not yet at 1.0. We're writing more tests, fixing bugs, working on TODOs
and finishing up a security audit.
It is named Santa because it keeps track of binaries that are naughty or nice.
@@ -29,21 +29,21 @@ The Santa docs are stored in the [Docs](https://github.com/google/santa/blob/mas
Admin-Related Features
========
* Multiple modes: In the default MONITOR mode, all binaries except
* 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.
* 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-based rules, with override levels: Instead of relying on a binary's 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
correctly, but a rule for a binary's 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.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature to that found in Managed Client (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 not relying 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.
@@ -54,7 +54,7 @@ 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
large 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
@@ -62,7 +62,6 @@ 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.
@@ -84,7 +83,7 @@ continue to work across OS versions.
Known Issues
============
Santa is not yet a 1.0 and we have some known issues to be aware of:
Santa is not yet at 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

View File

@@ -149,7 +149,6 @@
0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.m */; };
0DEFB7C01ACB28B000B92AAE /* SNTCommandSyncConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */; };
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C51ACDD80100B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C61ACDE5F600B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C81ACF0BFE00B92AAE /* SNTFileWatcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */; };
0DF395641AB76A7900CBC520 /* NSData+Zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DF395631AB76A7900CBC520 /* NSData+Zlib.m */; };
@@ -172,6 +171,8 @@
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */; };
C78227631E1C3C7D006EB2D6 /* santabs.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = C78227541E1C3C58006EB2D6 /* santabs.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C7943FE92028B855008D4F76 /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
C7943FEA2028C4F7008D4F76 /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C79A23581E23F7E80037AFA8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C79A23561E23F7E80037AFA8 /* main.m */; };
@@ -1221,13 +1222,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santad-santabs-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
2146C1274A8C11B3755A8A67 /* [CP] Check Pods Manifest.lock */ = {
@@ -1236,13 +1240,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santad-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
23869BA352E2C86DEFE62819 /* [CP] Copy Pods Resources */ = {
@@ -1296,13 +1303,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-LogicTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
B193461D3612BCB47C16E407 /* [CP] Check Pods Manifest.lock */ = {
@@ -1311,13 +1321,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Santa-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
BA20035148DDEF5808B2C7EF /* [CP] Embed Pods Frameworks */ = {
@@ -1358,13 +1371,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santactl-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D49A3AB950AFD99741E9AF89 /* [CP] Embed Pods Frameworks */ = {
@@ -1500,6 +1516,7 @@
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */,
0D416401191974F1006A356A /* SNTCommandSyncState.m in Sources */,
0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */,
C7943FE92028B855008D4F76 /* SNTFileWatcher.m in Sources */,
0DB77FDA1CD14092004DF060 /* SNTBlockMessage.m in Sources */,
0D35BDA218FD71CE00921A21 /* main.m in Sources */,
0DCD6043190ACCB8006B445C /* SNTFileInfo.m in Sources */,
@@ -1518,6 +1535,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C7943FEA2028C4F7008D4F76 /* SNTFileWatcher.m in Sources */,
0DCA552718C95928002A7DAE /* SNTXPCConnection.m in Sources */,
0D385DF1180DE51600418BC6 /* SNTAppDelegate.m in Sources */,
0D88680A1AC48A1200B86659 /* SNTSystemInfo.m in Sources */,
@@ -1532,7 +1550,6 @@
0D1B477019A53419008CADD3 /* SNTAboutWindowController.m in Sources */,
0D668E8118D1121700E29A8B /* SNTMessageWindow.m in Sources */,
0DA73CA11934F8100056D7C4 /* SNTLogging.m in Sources */,
0DEFB7C51ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
C7479F051E53704E0054C1CF /* SNTXPCBundleServiceInterface.m in Sources */,
0D20710E1A7C4A86008B0A9A /* SNTStoredEvent.m in Sources */,
0DE2CE561CA05561002B649A /* SNTAccessibleTextField.m in Sources */,

View File

@@ -16,7 +16,6 @@
#import "SNTAboutWindowController.h"
#import "SNTConfigurator.h"
#import "SNTFileWatcher.h"
#import "SNTNotificationManager.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
@@ -24,7 +23,6 @@
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;
@property SNTFileWatcher *configFileWatcher;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *daemonListener;
@property SNTXPCConnection *bundleListener;
@@ -36,12 +34,6 @@
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setupMenu];
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kMobileConfigFilePath
handler:^(unsigned long data) {
if (!(data & DISPATCH_VNODE_ATTRIB)) [[SNTConfigurator configurator] reloadConfigData];
}];
self.notificationManager = [[SNTNotificationManager alloc] init];
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];

View File

@@ -102,14 +102,16 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
case SNTClientModeMonitor:
un.informativeText = @"Switching into Monitor mode";
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
if (!customMsg) break;
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
break;
case SNTClientModeLockdown:
un.informativeText = @"Switching into Lockdown mode";
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
if (!customMsg) break;
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
break;
default:
return;

View File

@@ -16,12 +16,10 @@
#import "SNTCommonEnums.h"
extern NSString *const kSyncStateFilePath;
extern NSString *const kMobileConfigFilePath;
///
/// Singleton that provides an interface for managing configuration values on disk
/// @note This class is designed as a singleton but that is not strictly enforced.
/// @note All properties are KVO compliant.
///
@interface SNTConfigurator : NSObject
@@ -30,16 +28,12 @@ extern NSString *const kMobileConfigFilePath;
///
/// The operating mode.
///
@property(nonatomic) SNTClientMode clientMode;
@property(readonly, nonatomic) SNTClientMode clientMode;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
/// Set the operating mode as received from a sync server.
///
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *fileChangesRegex;
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
///
/// The regex of whitelisted paths. Regexes are specified in ICU format.
@@ -48,7 +42,12 @@ extern NSString *const kMobileConfigFilePath;
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
@property(readonly, nonatomic) NSRegularExpression *whitelistPathRegex;
///
/// Set the regex of whitelisted paths as received from a sync server.
///
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re;
///
/// The regex of blacklisted paths. Regexes are specified in ICU format.
@@ -57,7 +56,21 @@ extern NSString *const kMobileConfigFilePath;
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *blacklistPathRegex;
@property(readonly, nonatomic) NSRegularExpression *blacklistPathRegex;
///
/// Set the regex of blacklisted paths as received from a sync server.
///
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(readonly, nonatomic) NSRegularExpression *fileChangesRegex;
///
/// Enable __PAGEZERO protection, defaults to YES
@@ -202,13 +215,8 @@ extern NSString *const kMobileConfigFilePath;
+ (instancetype)configurator;
///
/// Re-read config data from disk.
/// Clear the sync server configuration from the effective configuration.
///
- (void)reloadConfigData;
///
/// Notify the receiver that the sync state file has changed.
///
- (void)syncStateFileChanged:(unsigned long)data;
- (void)clearSyncState;
@end

View File

@@ -16,23 +16,25 @@
#include <sys/stat.h>
#import "SNTFileWatcher.h"
#import "SNTLogging.h"
#import "SNTStrengthify.h"
#import "SNTSystemInfo.h"
@interface SNTConfigurator ()
@property NSMutableDictionary *configData;
/// Creating NSRegularExpression objects is not fast, so cache them.
@property NSRegularExpression *cachedFileChangesRegex;
@property NSRegularExpression *cachedWhitelistDirRegex;
@property NSRegularExpression *cachedBlacklistDirRegex;
/// A NSUserDefaults object set to use the com.google.santa suite.
@property(readonly, nonatomic) NSUserDefaults *defaults;
/// Keys used by a mobileconfig or sync server
@property(readonly, nonatomic) NSArray *syncServerKeys;
@property(readonly, nonatomic) NSArray *mobileConfigKeys;
// Keys and expected value types.
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
/// Holds the configurations from a sync server and mobileconfig.
@property NSMutableDictionary *syncState;
@property NSMutableDictionary *configState;
/// Watcher for the sync-state.plist.
@property(nonatomic) SNTFileWatcher *syncStateWatcher;
@end
@implementation SNTConfigurator
@@ -43,9 +45,6 @@ NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
/// The domain used by mobileconfig.
static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The hard-coded path to the mobileconfig file.
NSString *const kMobileConfigFilePath = @"/Library/Managed Preferences/com.google.santa.plist";
/// The keys managed by a mobileconfig.
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
@@ -72,11 +71,12 @@ static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
// The keys managed by a sync server.
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
@@ -86,22 +86,46 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (instancetype)init {
self = [super init];
if (self) {
_defaults = [[NSUserDefaults alloc] initWithSuiteName:kMobileConfigDomain];
_syncServerKeys = @[
kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey, kFileChangesRegexKey,
kFullSyncLastSuccess, kRuleSyncLastSuccess, kSyncCleanRequired
];
_mobileConfigKeys = @[
kClientModeKey, kFileChangesRegexKey, kWhitelistRegexKey, kBlacklistRegexKey,
kEnablePageZeroProtectionKey, kMoreInfoURLKey, kEventDetailURLKey, kEventDetailTextKey,
kUnknownBlockMessage, kBannedBlockMessage, kModeNotificationMonitor,
kModeNotificationLockdown, kSyncBaseURLKey, kClientAuthCertificateFileKey,
kClientAuthCertificatePasswordKey, kClientAuthCertificateCNKey,
kClientAuthCertificateIssuerKey, kServerAuthRootsDataKey, kServerAuthRootsFileKey,
kMachineOwnerKey, kMachineIDKey, kMachineOwnerPlistFileKey, kMachineOwnerPlistKeyKey,
kMachineIDPlistFileKey, kMachineIDPlistKeyKey
];
[self reloadConfigData];
_syncServerKeyTypes = @{
kClientModeKey : [NSNumber class],
kWhitelistRegexKey : [NSRegularExpression class],
kBlacklistRegexKey : [NSRegularExpression class],
kFullSyncLastSuccess : [NSDate class],
kRuleSyncLastSuccess : [NSDate class],
kSyncCleanRequired : [NSNumber class]
};
_forcedConfigKeyTypes = @{
kClientModeKey : [NSNumber class],
kFileChangesRegexKey : [NSRegularExpression class],
kWhitelistRegexKey : [NSRegularExpression class],
kBlacklistRegexKey : [NSRegularExpression class],
kEnablePageZeroProtectionKey : [NSNumber class],
kMoreInfoURLKey : [NSString class],
kEventDetailURLKey : [NSString class],
kEventDetailTextKey : [NSString class],
kUnknownBlockMessage : [NSString class],
kBannedBlockMessage : [NSString class],
kModeNotificationMonitor : [NSString class],
kModeNotificationLockdown : [NSString class],
kSyncBaseURLKey : [NSString class],
kClientAuthCertificateFileKey : [NSString class],
kClientAuthCertificatePasswordKey : [NSString class],
kClientAuthCertificateCNKey : [NSString class],
kClientAuthCertificateIssuerKey : [NSString class],
kServerAuthRootsDataKey : [NSData class],
kServerAuthRootsFileKey : [NSString class],
kMachineOwnerKey : [NSString class],
kMachineIDKey : [NSString class],
kMachineOwnerPlistFileKey : [NSString class],
kMachineOwnerPlistKeyKey : [NSString class],
kMachineIDPlistFileKey : [NSString class],
kMachineIDPlistKeyKey : [NSString class],
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
_configState = [self readForcedConfig];
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
[self startWatchingDefaults];
}
return self;
}
@@ -117,277 +141,301 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return sharedConfigurator;
}
#pragma mark KVO Dependencies
+ (NSSet *)keyPathsForValuesAffectingClientMode {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
}
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
}
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
}
+ (NSSet *)keyPathsForValuesAffectingFileChangesRegex {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailURL {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailText {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingBannedBlockMessage {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationMonitor {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateFile {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificatePassword {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateCn {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateIssuer {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsData {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsFile {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingMachineOwner {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingMachineID {
return [NSSet setWithObject:@"configState"];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
NSInteger cm = SNTClientModeUnknown;
id mode = self.configData[kClientModeKey];
if ([mode respondsToSelector:@selector(longLongValue)]) {
cm = (NSInteger)[mode longLongValue];
}
SNTClientMode cm = [self.syncState[kClientModeKey] longLongValue];
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
return (SNTClientMode)cm;
} else {
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
self.clientMode = SNTClientModeMonitor;
return SNTClientModeMonitor;
return cm;
}
cm = [self.configState[kClientModeKey] longLongValue];
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
return cm;
}
return SNTClientModeMonitor;
}
- (void)setClientMode:(SNTClientMode)newMode {
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
self.configData[kClientModeKey] = @(newMode);
[self saveSyncStateToDisk];
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
} else {
LOGW(@"Ignoring request to change client mode to %ld", newMode);
}
}
- (NSRegularExpression *)whitelistPathRegex {
if (!self.cachedWhitelistDirRegex && self.configData[kWhitelistRegexKey]) {
NSString *re = self.configData[kWhitelistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedWhitelistDirRegex;
return self.syncState[kWhitelistRegexKey] ?: self.configState[kWhitelistRegexKey];
}
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kWhitelistRegexKey];
} else {
self.configData[kWhitelistRegexKey] = [re pattern];
}
self.cachedWhitelistDirRegex = nil;
[self saveSyncStateToDisk];
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kWhitelistRegexKey value:re];
}
- (NSRegularExpression *)blacklistPathRegex {
if (!self.cachedBlacklistDirRegex && self.configData[kBlacklistRegexKey]) {
NSString *re = self.configData[kBlacklistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedBlacklistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedBlacklistDirRegex;
return self.syncState[kBlacklistRegexKey] ?: self.configState[kBlacklistRegexKey];
}
- (void)setBlacklistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kBlacklistRegexKey];
} else {
self.configData[kBlacklistRegexKey] = [re pattern];
}
self.cachedBlacklistDirRegex = nil;
[self saveSyncStateToDisk];
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kBlacklistRegexKey value:re];
}
- (NSRegularExpression *)fileChangesRegex {
if (!self.cachedFileChangesRegex && self.configData[kFileChangesRegexKey]) {
NSString *re = self.configData[kFileChangesRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedFileChangesRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedFileChangesRegex;
}
- (void)setFileChangesRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kFileChangesRegexKey];
} else {
self.configData[kFileChangesRegexKey] = [re pattern];
}
self.cachedFileChangesRegex = nil;
[self saveSyncStateToDisk];
}
- (BOOL)enablePageZeroProtection {
NSNumber *keyValue = self.configData[kEnablePageZeroProtectionKey];
return keyValue ? [keyValue boolValue] : YES;
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configData[kMoreInfoURLKey]];
}
- (NSString *)eventDetailURL {
return self.configData[kEventDetailURLKey];
}
- (NSString *)eventDetailText {
return self.configData[kEventDetailTextKey];
}
- (NSString *)unknownBlockMessage {
return self.configData[kUnknownBlockMessage];
}
- (NSString *)bannedBlockMessage {
return self.configData[kBannedBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configData[kModeNotificationMonitor];
}
- (NSString *)modeNotificationLockdown {
return self.configData[kModeNotificationLockdown];
return self.configState[kFileChangesRegexKey];
}
- (NSURL *)syncBaseURL {
NSString *urlStr = self.configData[kSyncBaseURLKey];
if (urlStr) {
NSURL *url = [NSURL URLWithString:urlStr];
if (!url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
}
return nil;
NSString *urlString = self.configState[kSyncBaseURLKey];
NSURL *url = [NSURL URLWithString:urlString];
if (urlString && !url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
}
- (BOOL)enablePageZeroProtection {
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
return number ? [number boolValue] : YES;
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
}
- (NSString *)eventDetailURL {
return self.configState[kEventDetailURLKey];
}
- (NSString *)eventDetailText {
return self.configState[kEventDetailTextKey];
}
- (NSString *)unknownBlockMessage {
return self.configState[kUnknownBlockMessage];
}
- (NSString *)bannedBlockMessage {
return self.configState[kBannedBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configState[kModeNotificationMonitor];
}
- (NSString *)modeNotificationLockdown {
return self.configState[kModeNotificationLockdown];
}
- (NSString *)syncClientAuthCertificateFile {
return self.configData[kClientAuthCertificateFileKey];
return self.configState[kClientAuthCertificateFileKey];
}
- (NSString *)syncClientAuthCertificatePassword {
return self.configData[kClientAuthCertificatePasswordKey];
return self.configState[kClientAuthCertificatePasswordKey];
}
- (NSString *)syncClientAuthCertificateCn {
return self.configData[kClientAuthCertificateCNKey];
return self.configState[kClientAuthCertificateCNKey];
}
- (NSString *)syncClientAuthCertificateIssuer {
return self.configData[kClientAuthCertificateIssuerKey];
return self.configState[kClientAuthCertificateIssuerKey];
}
- (NSData *)syncServerAuthRootsData {
return self.configData[kServerAuthRootsDataKey];
return self.configState[kServerAuthRootsDataKey];
}
- (NSString *)syncServerAuthRootsFile {
return self.configData[kServerAuthRootsFileKey];
return self.configState[kServerAuthRootsFileKey];
}
- (NSDate *)fullSyncLastSuccess {
return self.configData[kFullSyncLastSuccess];
return self.syncState[kFullSyncLastSuccess];
}
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
self.configData[kFullSyncLastSuccess] = fullSyncLastSuccess;
[self saveSyncStateToDisk];
self.syncState[kFullSyncLastSuccess] = fullSyncLastSuccess;
self.ruleSyncLastSuccess = fullSyncLastSuccess;
}
- (NSDate *)ruleSyncLastSuccess {
return self.configData[kRuleSyncLastSuccess];
return self.syncState[kRuleSyncLastSuccess];
}
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
self.syncState[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
[self saveSyncStateToDisk];
}
- (BOOL)syncCleanRequired {
return [self.configData[kSyncCleanRequired] boolValue];
return [self.syncState[kSyncCleanRequired] boolValue];
}
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
self.syncState[kSyncCleanRequired] = @(syncCleanRequired);
[self saveSyncStateToDisk];
}
- (NSString *)machineOwner {
NSString *machineOwner;
NSString *machineOwner = self.configState[kMachineOwnerKey];
if (machineOwner) return machineOwner;
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
machineOwner = plist[self.configData[kMachineOwnerPlistKeyKey]];
NSString *plistPath = self.configState[kMachineOwnerPlistFileKey];
NSString *plistKey = self.configState[kMachineOwnerPlistKeyKey];
if (plistPath && plistKey) {
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
machineOwner = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
}
if (self.configData[kMachineOwnerKey]) {
machineOwner = self.configData[kMachineOwnerKey];
}
if (!machineOwner) machineOwner = @"";
return machineOwner;
return machineOwner ?: @"";
}
- (NSString *)machineID {
NSString *machineId;
NSString *machineId = self.configState[kMachineIDKey];
if (machineId) return machineId;
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
machineId = plist[self.configData[kMachineIDPlistKeyKey]];
NSString *plistPath = self.configState[kMachineIDPlistFileKey];
NSString *plistKey = self.configState[kMachineIDPlistKeyKey];
if (plistPath && plistKey) {
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
machineId = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
}
if (self.configData[kMachineIDKey]) {
machineId = self.configData[kMachineIDKey];
}
if ([machineId length] == 0) {
machineId = [SNTSystemInfo hardwareUUID];
}
return machineId;
}
- (void)reloadConfigData {
// Load the mobileconfig
self.configData = [self mobileConfig];
if (!self.configData[kClientModeKey]) {
// Default to Monitor if the config is missing or invalid
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
}
// Nothing else to do if a sync server is not involved
if (!self.configData[kSyncBaseURLKey]) return;
// Load the last known sync state
if (![[NSFileManager defaultManager] fileExistsAtPath:kSyncStateFilePath]) return;
NSMutableDictionary *syncState = [self syncState];
if (!self) return;
// Overwrite or add the sync state to the running config
for (NSString *key in [self syncServerKeys]) {
self.configData[key] = syncState[key];
}
[self saveSyncStateToDisk];
return machineId.length ? machineId : [SNTSystemInfo hardwareUUID];
}
#pragma mark Private
- (NSMutableDictionary *)syncState {
NSError *error;
NSData *readData = [NSData dataWithContentsOfFile:kSyncStateFilePath
options:NSDataReadingMappedIfSafe
error:&error];
if (!readData) {
LOGE(@"Could not read sync state file: %@, replacing.", [error localizedDescription]);
[self saveSyncStateToDisk];
return nil;
}
///
/// Update the syncState. Triggers a KVO event for all dependents.
///
- (BOOL)updateSyncStateForKey:(NSString *)key value:(id)value {
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[key] = value;
self.syncState = syncState;
[self saveSyncStateToDisk];
return YES;
}
///
/// Read the saved syncState.
///
- (NSMutableDictionary *)readSyncStateFromDisk {
// Only read the sync state if a sync server is configured.
if (!self.syncBaseURL) return nil;
// Only santad should read this file.
if (geteuid() != 0) return nil;
NSMutableDictionary *syncState =
[NSPropertyListSerialization propertyListWithData:readData
options:NSPropertyListMutableContainers
format:NULL
error:&error];
if (!syncState) {
LOGE(@"Could not parse sync state file: %@, replacing.", [error localizedDescription]);
[self saveSyncStateToDisk];
return nil;
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
for (NSString *key in syncState.allKeys) {
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
syncState[key] = [self expressionForPattern:pattern];
} else if (![syncState[key] isKindOfClass:self.syncServerKeyTypes[key]]) {
syncState[key] = nil;
continue;
}
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WEAKIFY(self);
self.syncStateWatcher = [[SNTFileWatcher alloc] initWithFilePath:kSyncStateFilePath
handler:^(unsigned long data) {
STRONGIFY(self);
[self syncStateFileChanged:data];
}];
});
return syncState;
}
@@ -395,13 +443,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
/// Saves the current effective syncState to disk.
///
- (void)saveSyncStateToDisk {
// Only save the sync state if a sync server is configured.
if (!self.syncBaseURL) return;
// Only santad should write to this file.
if (geteuid() != 0) return;
NSMutableDictionary *syncState =
[NSMutableDictionary dictionaryWithCapacity:[self syncServerKeys].count];
for (NSString *key in [self syncServerKeys]) {
syncState[key] = self.configData[key];
}
// Either remove
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[kWhitelistRegexKey] = [syncState[kWhitelistRegexKey] pattern];
syncState[kBlacklistRegexKey] = [syncState[kBlacklistRegexKey] pattern];
[syncState writeToFile:kSyncStateFilePath atomically:YES];
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
ofItemAtPath:kSyncStateFilePath error:NULL];
@@ -419,19 +468,18 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
int mask = S_IRWXU | S_IRWXG | S_IRWXO;
int desired = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
if (fileStat.st_uid != 0 || fileStat.st_gid != 0 || (fileStat.st_mode & mask) != desired) {
LOGD(@"Sync state file permissions changed, fixing.");
LOGI(@"Sync state file permissions changed, fixing.");
chown(cPath, 0, 0);
chmod(cPath, desired);
}
} else {
NSDictionary *syncState = [self syncState];
for (NSString *key in self.syncServerKeys) {
if (((self.configData[key] && !syncState[key]) ||
(!self.configData[key] && syncState[key]) ||
(self.configData[key] && ![self.configData[key] isEqualTo:syncState[key]]))) {
NSDictionary *newSyncState = [self readSyncStateFromDisk];
for (NSString *key in self.syncState) {
if (((self.syncState[key] && !newSyncState[key]) ||
(!self.syncState[key] && newSyncState[key]) ||
(self.syncState[key] && ![self.syncState[key] isEqualTo:newSyncState[key]]))) {
// Ignore sync url and dates
if ([key isEqualToString:kSyncBaseURLKey] ||
[key isEqualToString:kRuleSyncLastSuccess] ||
if ([key isEqualToString:kRuleSyncLastSuccess] ||
[key isEqualToString:kFullSyncLastSuccess]) continue;
LOGE(@"Sync state file changed, replacing");
[self saveSyncStateToDisk];
@@ -441,25 +489,57 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
///
/// Returns a config provided by a com.google.santa mobileconfig or an empty
/// NSMutableDictionary object if no config applies.
///
- (NSMutableDictionary *)mobileConfig {
NSMutableDictionary *config =
[NSMutableDictionary dictionaryWithCapacity:[self mobileConfigKeys].count];
for (NSString *key in [self mobileConfigKeys]) {
if ([self.defaults objectIsForcedForKey:key]) {
config[key] = [self.defaults objectForKey:key];
- (void)clearSyncState {
self.syncState = [NSMutableDictionary dictionary];
}
#pragma mark Private Defaults Methods
- (NSRegularExpression *)expressionForPattern:(NSString *)pattern {
if (!pattern) return nil;
if (![pattern hasPrefix:@"^"]) pattern = [@"^" stringByAppendingString:pattern];
return [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
}
- (NSMutableDictionary *)readForcedConfig {
NSMutableDictionary *forcedConfig = [NSMutableDictionary dictionary];
for (NSString *key in self.forcedConfigKeyTypes) {
id obj = [self forcedConfigValueForKey:key];
forcedConfig[key] = [obj isKindOfClass:self.forcedConfigKeyTypes[key]] ? obj : nil;
// Create the regex objects now
if (self.forcedConfigKeyTypes[key] == [NSRegularExpression class]) {
NSString *pattern = [obj isKindOfClass:[NSString class]] ? obj : nil;
forcedConfig[key] = [self expressionForPattern:pattern];
}
}
if (config[kSyncBaseURLKey]) {
for (NSString *key in [self syncServerKeys]) {
if ([key isEqualToString:kSyncBaseURLKey]) continue;
[config removeObjectForKey:key];
}
}
return config;
return forcedConfig;
}
- (id)forcedConfigValueForKey:(NSString *)key {
id obj = [self.defaults objectForKey:key];
return [self.defaults objectIsForcedForKey:key inDomain:kMobileConfigDomain] ? obj : nil;
}
- (void)startWatchingDefaults {
// Only santad should listen.
if (geteuid() != 0) return;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
}
- (void)defaultsChanged:(void *)v {
SEL handleChange = @selector(handleChange);
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:handleChange object:nil];
[self performSelector:handleChange withObject:nil afterDelay:5.0f];
}
///
/// Update the configState. Triggers a KVO event for all dependents.
///
- (void)handleChange {
self.configState = [self readForcedConfig];
}
@end

View File

@@ -37,6 +37,7 @@ enum SantaDriverMethods {
kSantaUserClientOpen,
kSantaUserClientAllowBinary,
kSantaUserClientDenyBinary,
kSantaUserClientAcknowledgeBinary,
kSantaUserClientClearCache,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
@@ -64,7 +65,8 @@ typedef enum {
ACTION_RESPOND_ALLOW = 20,
ACTION_RESPOND_DENY = 21,
ACTION_RESPOND_TOOLONG = 22,
ACTION_RESPOND_ACK = 23,
// NOTIFY
ACTION_NOTIFY_EXEC = 30,
ACTION_NOTIFY_WRITE = 31,

View File

@@ -75,8 +75,11 @@
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)syncCleanRequired:(void (^)(BOOL))reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;

View File

@@ -233,10 +233,15 @@ void SantaDecisionManager::AddToCache(
case ACTION_REQUEST_BINARY:
decision_cache->set(identifier, val, 0);
break;
case ACTION_RESPOND_ACK:
decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_DENY:
decision_cache->set(
identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56));
// TODO(bur): Avoid calling set() twice, finding and locking buckets is fast, but not free.
if (decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
break;
default:
break;
@@ -316,10 +321,16 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
return ACTION_ERROR;
}
// Check the cache every kRequestLoopSleepMilliseconds. Break this loop and send the request
// again if kRequestCacheChecks is reached. Don't break the loop if the daemon is working on the
// request, indicated with ACTION_RESPOND_ACK.
auto cache_check_count = 0;
do {
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
} while (ClientConnected() &&
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
|| (return_action == ACTION_RESPOND_ACK)));
} while (!RESPONSE_VALID(return_action) && ClientConnected());
// If response is still not valid, the daemon exited
@@ -354,7 +365,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
// 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) {
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
// until AddToCache is called, indicating a response has arrived.
msleep((void *)vnode_id, NULL, 0, "", &ts_);

View File

@@ -144,6 +144,12 @@ class SantaDecisionManager : public OSObject {
*/
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
/**
While waiting for a response from the daemon, this is the maximum number cache checks before
re-sending the request.
*/
static const uint32_t kRequestCacheChecks = 5;
/**
The maximum number of milliseconds a cached deny message should be
considered valid.

View File

@@ -152,6 +152,18 @@ IOReturn SantaDriverClient::deny_binary(
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
if (!vnode_id) return kIOReturnInvalid;
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ACK, 0);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
@@ -199,6 +211,7 @@ IOReturn SantaDriverClient::externalMethod(
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::deny_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::acknowledge_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
{ &SantaDriverClient::check_cache, 1, 0, 1, 0 }

View File

@@ -87,6 +87,11 @@ class com_google_SantaDriverClient : public IOUserClient {
static IOReturn deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to acknowledge a binary request. This is used for large binaries that
/// may take a while to reach a decision.
static IOReturn acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to empty the cache.
static IOReturn clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);

View File

@@ -98,11 +98,20 @@
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(self.queue, ^{
// Use the highest bundle we can find. Save and reuse the bundle infomation when creating
// the related binary events.
// Use the highest bundle we can find.
SNTFileInfo *b = [[SNTFileInfo alloc] initWithPath:event.fileBundlePath];
b.useAncestorBundle = YES;
event.fileBundlePath = b.bundlePath;
// If path to the bundle is unavailable, stop. SantaGUI will revert to
// using the offending blockable.
if (!event.fileBundlePath) {
reply(nil, nil, 0);
dispatch_semaphore_signal(sema);
return;
}
// Reuse the bundle infomation when creating the related binary events.
event.fileBundleID = b.bundleIdentifier;
event.fileBundleName = b.bundleName;
event.fileBundleVersion = b.bundleVersion;

View File

@@ -345,10 +345,11 @@ REGISTER_COMMAND_NAME(@"fileinfo")
dispatch_once(&token, ^{ [cmd.daemonConn resume]; });
__block SNTEventState state;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
NSError *err;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&err];
[[cmd.daemonConn remoteObjectProxy] decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:csc.leafCertificate.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);

View File

@@ -103,15 +103,26 @@ REGISTER_COMMAND_NAME(@"status")
}];
// Sync 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] fullSyncLastSuccess];
NSString *lastSyncSuccessStr = [dateFormatter stringFromDate:lastSyncSuccess] ?: @"Never";
NSDate *lastRuleSyncSuccess = [[SNTConfigurator configurator] ruleSyncLastSuccess];
NSString *lastRuleSyncSuccessStr =
[dateFormatter stringFromDate:lastRuleSyncSuccess] ?: lastSyncSuccessStr;
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
__block NSDate *fullSyncLastSuccess;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] fullSyncLastSuccess:^(NSDate *date) {
fullSyncLastSuccess = date;
dispatch_group_leave(group);
}];
__block NSDate *ruleSyncLastSuccess;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] ruleSyncLastSuccess:^(NSDate *date) {
ruleSyncLastSuccess = date;
dispatch_group_leave(group);
}];
__block BOOL syncCleanReqd = NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] syncCleanRequired:^(BOOL clean) {
syncCleanReqd = clean;
dispatch_group_leave(group);
}];
__block BOOL pushNotifications = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
@@ -136,6 +147,15 @@ REGISTER_COMMAND_NAME(@"status")
fprintf(stderr, "Failed to retrieve some stats from daemon\n\n");
}
// Format dates
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
NSString *fullSyncLastSuccessStr = [dateFormatter stringFromDate:fullSyncLastSuccess] ?: @"Never";
NSString *ruleSyncLastSuccessStr =
[dateFormatter stringFromDate:ruleSyncLastSuccess] ?: fullSyncLastSuccessStr;
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
if ([arguments containsObject:@"--json"]) {
NSDictionary *stats = @{
@"daemon" : @{
@@ -158,8 +178,8 @@ REGISTER_COMMAND_NAME(@"status")
@"sync" : @{
@"server" : syncURLStr ?: @"null",
@"clean_required" : @(syncCleanReqd),
@"last_successful_full" : lastSyncSuccessStr ?: @"null",
@"last_successful_rule" : lastRuleSyncSuccessStr ?: @"null",
@"last_successful_full" : fullSyncLastSuccessStr ?: @"null",
@"last_successful_rule" : ruleSyncLastSuccessStr ?: @"null",
@"push_notifications" : pushNotifications ? @"Connected" : @"Disconnected",
@"bundle_scanning" : @(bundlesEnabled)
},
@@ -187,8 +207,8 @@ REGISTER_COMMAND_NAME(@"status")
printf(">>> Sync Info\n");
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", "Last Successful Full Sync", [fullSyncLastSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Last Successful Rule Sync", [ruleSyncLastSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Push Notifications",
(pushNotifications ? "Connected" : "Disconnected"));
printf(" %-25s | %s\n", "Bundle Scanning", (bundlesEnabled ? "Yes" : "No"));

View File

@@ -178,7 +178,7 @@ static void reachabilityHandler(
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply((self.FCMClient.FCMToken != nil));
reply(self.FCMClient.isConnected);
}
#pragma mark push notification methods
@@ -205,16 +205,17 @@ static void reachabilityHandler(
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.connectionErrorHandler = ^(NSError *error) {
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
LOGE(@"FCM connection error: %@", error);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
LOGD(@"%@", log);
LOGD(@"FCMClient: %@", log);
};
[self.FCMClient connect];

View File

@@ -63,7 +63,7 @@
// Update last sync success
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setSyncLastSuccess:[NSDate date] reply:replyBlock];
[[self.daemonConn remoteObjectProxy] setFullSyncLastSuccess:[NSDate date] reply:replyBlock];
// Wait for dispatch group
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));

View File

@@ -60,11 +60,17 @@
dispatch_group_leave(group);
}];
__block BOOL syncClean = NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] syncCleanRequired:^(BOOL clean) {
syncClean = clean;
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]) {
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] || syncClean) {
LOGD(@"Clean sync requested by user");
requestDict[kRequestCleanSync] = @YES;
}

View File

@@ -16,9 +16,6 @@
@import DiskArbitration;
#include <sys/stat.h>
#include <sys/types.h>
#import "SNTCommonEnums.h"
#import "SNTConfigurator.h"
#import "SNTDaemonControlController.h"
@@ -28,22 +25,22 @@
#import "SNTEventLog.h"
#import "SNTEventTable.h"
#import "SNTExecutionController.h"
#import "SNTFileWatcher.h"
#import "SNTLogging.h"
#import "SNTNotificationQueue.h"
#import "SNTRuleTable.h"
#import "SNTSyncdQueue.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTApplication ()
@property DASessionRef diskArbSession;
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@property SNTExecutionController *execController;
@property SNTFileWatcher *configFileWatcher;
@property SNTFileWatcher *syncStateWatcher;
@property SNTXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@end
@implementation SNTApplication
@@ -73,18 +70,37 @@
_eventLog = [[SNTEventLog alloc] init];
SNTNotificationQueue *notQueue = [[SNTNotificationQueue alloc] init];
self.notQueue = [[SNTNotificationQueue alloc] init];
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Restart santactl if it goes down
syncdQueue.invalidationHandler = ^{
[self startSyncd];
};
// Listen for actionable config changes.
NSKeyValueObservingOptions bits = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld);
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"clientMode"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"syncBaseURL"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"whitelistPathRegex"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"blacklistPathRegex"
options:bits
context:NULL];
// Establish XPC listener for Santa and santactl connections
SNTDaemonControlController *dc =
[[SNTDaemonControlController alloc] initWithDriverManager:_driverManager
notificationQueue:notQueue
notificationQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
@@ -94,43 +110,11 @@
_controlConnection.exportedObject = dc;
[_controlConnection resume];
__block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode];
__block NSURL *origSyncURL = [[SNTConfigurator configurator] syncBaseURL];
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kMobileConfigFilePath
handler:^(unsigned long data) {
LOGD(@"Mobileconfig changed, reloading.");
[[SNTConfigurator configurator] reloadConfigData];
// Flush cache if client just went into lockdown.
SNTClientMode newMode = [[SNTConfigurator configurator] clientMode];
if (origMode != newMode) {
origMode = newMode;
if (newMode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCacheNonRootOnly:NO];
}
}
// 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];
}
}];
_syncStateWatcher = [[SNTFileWatcher alloc] initWithFilePath:kSyncStateFilePath
handler:^(unsigned long data) {
[[SNTConfigurator configurator] syncStateFileChanged:data];
}];
// Initialize the binary checker object
_execController = [[SNTExecutionController alloc] initWithDriverManager:_driverManager
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:notQueue
notifierQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
// Start up santactl as a daemon if a sync server exists.
@@ -142,18 +126,6 @@
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.");
@@ -270,4 +242,76 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
[app.driverManager flushCacheNonRootOnly:YES];
}
- (void)startSyncd {
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self stopSyncd];
self.syncdPID = fork();
if (self.syncdPID == -1) {
LOGI(@"Failed to fork");
self.syncdPID = 0;
} else if (self.syncdPID == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
}
LOGI(@"santactl started with pid: %i", self.syncdPID);
}
- (void)stopSyncd {
if (!self.syncdPID) return;
int ret = kill(self.syncdPID, SIGKILL);
LOGD(@"kill(%i, 9) = %i", self.syncdPID, ret);
self.syncdPID = 0;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"clientMode"]) {
SNTClientMode new =
[change[@"new"] isKindOfClass:[NSNumber class]] ? [change[@"new"] longLongValue] : 0;
SNTClientMode old =
[change[@"old"] isKindOfClass:[NSNumber class]] ? [change[@"old"] longLongValue] : 0;
if (new != old) [self clientModeDidChange:new];
} else if ([keyPath isEqualToString:@"syncBaseURL"]) {
NSURL *new = [change[@"new"] isKindOfClass:[NSURL class]] ? change[@"new"] : nil;
NSURL *old = [change[@"old"] isKindOfClass:[NSURL class]] ? change[@"old"] : nil;
if (!new && !old) return;
if (![new.absoluteString isEqualToString:old.absoluteString]) [self syncBaseURLDidChange:new];
} else if ([keyPath isEqualToString:@"whitelistPathRegex"] ||
[keyPath isEqualToString:@"blacklistPathRegex"]) {
NSRegularExpression *new =
[change[@"new"] isKindOfClass:[NSRegularExpression class]] ? change[@"new"] : nil;
NSRegularExpression *old =
[change[@"old"] isKindOfClass:[NSRegularExpression class]] ? change[@"old"] : nil;
if (!new && !old) return;
if (![new.pattern isEqualToString:old.pattern]) {
LOGI(@"Changed [white|black]list regex, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
}
}
}
- (void)clientModeDidChange:(SNTClientMode)clientMode {
if (clientMode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCacheNonRootOnly:NO];
}
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:clientMode];
}
- (void)syncBaseURLDidChange:(NSURL *)syncBaseURL {
if (syncBaseURL) {
LOGI(@"Starting santactl with new SyncBaseURL: %@", syncBaseURL);
[self startSyncd];
} else {
LOGI(@"SyncBaseURL removed, killing santactl pid: %i", self.syncdPID);
[self stopSyncd];
[[SNTConfigurator configurator] clearSyncState];
}
}
@end

View File

@@ -146,15 +146,7 @@ double watchdogRAMPeak = 0;
}
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply {
if ([[SNTConfigurator configurator] clientMode] != mode) {
// Flush cache if client just went into lockdown.
if (mode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCacheNonRootOnly:NO];
}
[[SNTConfigurator configurator] setClientMode:mode];
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:mode];
}
[[SNTConfigurator configurator] setSyncServerClientMode:mode];
reply();
}
@@ -167,16 +159,28 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply {
reply([[SNTConfigurator configurator] fullSyncLastSuccess]);
}
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setFullSyncLastSuccess:date];
reply();
}
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply {
reply([[SNTConfigurator configurator] ruleSyncLastSuccess]);
}
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setRuleSyncLastSuccess:date];
reply();
}
- (void)syncCleanRequired:(void (^)(BOOL))reply {
reply([[SNTConfigurator configurator] syncCleanRequired]);
}
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply {
[[SNTConfigurator configurator] setSyncCleanRequired:cleanReqd];
reply();
@@ -186,9 +190,7 @@ double watchdogRAMPeak = 0;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
[[SNTConfigurator configurator] setWhitelistPathRegex:re];
LOGI(@"Received new whitelist regex, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
[[SNTConfigurator configurator] setSyncServerWhitelistPathRegex:re];
reply();
}
@@ -196,9 +198,7 @@ double watchdogRAMPeak = 0;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
[[SNTConfigurator configurator] setBlacklistPathRegex:re];
LOGI(@"Received new blacklist regex, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
[[SNTConfigurator configurator] setSyncServerBlacklistPathRegex:re];
reply();
}

View File

@@ -162,6 +162,13 @@ static const int MAX_DELAY = 15;
1,
0,
0);
case ACTION_RESPOND_ACK:
return IOConnectCallScalarMethod(_connection,
kSantaUserClientAcknowledgeBinary,
&vnodeId,
1,
0,
0);
default:
return KERN_INVALID_ARGUMENT;
}

View File

@@ -38,6 +38,12 @@
#import "SNTStoredEvent.h"
#import "SNTSyncdQueue.h"
// A binary is considered large at ~30MB. Large binaries take longer to hash and consequently
// longer to post a decision back to santa-driver. When a binary is considered large santad will
// let santa-driver know it has received its request and is working on a decision. This allows
// santa-driver to relax; it does not have to worry about resending the request due to a timeout.
static size_t kLargeBinarySize = 30 * 1024 * 1024;
@interface SNTExecutionController ()
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@@ -102,6 +108,13 @@
return;
}
// If the binary is large let santa-driver know we received the request and we are working on it.
if (binInfo.fileSize > kLargeBinarySize) {
LOGD(@"%@ is larger than %zu. Letting santa-driver know we are working on it.",
binInfo.path, kLargeBinarySize);
[_driverManager postToKernelAction:ACTION_RESPOND_ACK forVnodeID:message.vnode_id];
}
// Get codesigning info about the file.
NSError *csError;
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path

View File

@@ -99,6 +99,9 @@
IOConnectCallScalarMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid, 1, 0, 0);
} else if (action == ACTION_RESPOND_DENY) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid, 1, 0, 0);
} else if (action == ACTION_RESPOND_ACK) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, 1, 0, 0);
}
}
@@ -239,6 +242,18 @@
} else if (strncmp("/bin/cat", vdata.path, strlen("/bin/cat")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
self.timesSeenCat++;
} else if (strncmp("/usr/bin/cal", vdata.path, strlen("/usr/bin/cal")) == 0) {
static int count = 0;
if (count++) TFAILINFO("Large binary should not re-request");
[self postToKernelAction:ACTION_RESPOND_ACK forVnodeID:vdata.vnode_id];
for (int i = 0; i < 15; ++i) {
printf("\033[s"); // save cursor position
printf("%i/15", i);
sleep(1);
printf("\033[u"); // restore cursor position
}
printf("\033[K\033[u"); // clear line, restore cursor position
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
} else if (strncmp("/bin/ln", vdata.path, strlen("/bin/ln")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
@@ -554,6 +569,20 @@
}
}
- (void)testLargeBinary {
TSTART("Handles large binary");
@try {
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
[testexec launch];
[testexec waitUntilExit];
} @catch (NSException *e) {
TFAILINFO("Failed to launch");
}
TPASS();
}
#pragma mark - Main
- (void)runTests {
@@ -580,6 +609,7 @@
printf("\n-> Performance tests:\033[m\n");
[self testCachePerformance];
[self testLargeBinary];
[self handlesLotsOfBinaries];
printf("\nAll tests passed.\n\n");