mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ee6531627 | ||
|
|
1cf8ee09e1 | ||
|
|
4a2cf9d722 | ||
|
|
6a6a32c1cf | ||
|
|
ce03611b52 | ||
|
|
bbe9f83878 | ||
|
|
40e6c6aa92 | ||
|
|
9f6ccf092a | ||
|
|
d4ba4b082f | ||
|
|
cce43829eb | ||
|
|
c1bfbac2fe | ||
|
|
fc87cde668 | ||
|
|
400c413029 | ||
|
|
0e6eb45732 | ||
|
|
7ca2028c19 | ||
|
|
08144b54a7 | ||
|
|
103137498b | ||
|
|
8e57e3709d | ||
|
|
bd6bd66946 | ||
|
|
6973dd0ec2 | ||
|
|
2e8b08cd9e | ||
|
|
edc8f43f42 | ||
|
|
133814cd73 | ||
|
|
57213ee31b | ||
|
|
b4fa2a394b | ||
|
|
0c39342d53 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
Build
|
||||
santa-*
|
||||
!santa-driver
|
||||
!*.md
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
@@ -8,3 +9,4 @@ Santa.xcodeproj/project.xcworkspace
|
||||
Santa.xcworkspace/xcuserdata
|
||||
Santa.xcworkspace/xcshareddata
|
||||
Source/DevelopmentTeam.xcconfig
|
||||
default.profraw
|
||||
|
||||
@@ -32,6 +32,7 @@ PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/rele
|
||||
# | |-- com.google.santad.plist
|
||||
# | |-- com.google.santagui.plist
|
||||
# | +-- com.google.santa.asl.conf
|
||||
# | +-- com.google.santa.newsyslog.conf
|
||||
# +--dsym
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
@@ -44,6 +45,7 @@ PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
|
||||
pack-Library-LaunchDaemons-com.google.santad.plist \
|
||||
pack-Library-LaunchAgents-com.google.santagui.plist \
|
||||
pack-etc-asl-com.google.santa.asl.conf \
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf \
|
||||
pack-script-preinstall \
|
||||
pack-script-postinstall
|
||||
|
||||
@@ -52,6 +54,7 @@ Santa.app: download
|
||||
com.google.santad.plist: download
|
||||
com.google.santagui.plist: download
|
||||
com.google.santa.asl.conf: download
|
||||
com.google.santa.newsyslog.conf: download
|
||||
|
||||
download:
|
||||
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
@@ -65,6 +68,12 @@ pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/asl
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
|
||||
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf: com.google.santa.newsyslog.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.newsyslog.conf ${WORK_D}/private/etc/newsyslog.d
|
||||
|
||||
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
|
||||
@sudo mkdir -p ${WORK_D}/Library/Extensions
|
||||
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
@@ -79,6 +88,7 @@ myclean:
|
||||
@rm -rf santa-driver.kext
|
||||
@rm -f config.plist
|
||||
@rm -f com.google.santa.asl.conf
|
||||
@rm -f com.google.santa.newsyslog.conf
|
||||
@rm -f com.google.santad.plist
|
||||
@rm -f com.google.santagui.plist
|
||||
@rm -f install.sh
|
||||
|
||||
2
Conf/com.google.santa.newsyslog.conf
Normal file
2
Conf/com.google.santa.newsyslog.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
|
||||
/var/db/santa/santa.log root:wheel 644 10 25000 * NZ
|
||||
@@ -42,6 +42,7 @@ mkdir -p /usr/local/bin
|
||||
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
|
||||
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
|
||||
/bin/cp ${SOURCE}/conf/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
# Reload syslogd to pick up ASL configuration change.
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
@@ -19,6 +19,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
/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 /private/etc/newsyslog.d/com.google.santa.newsyslog.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
|
||||
|
||||
83
Docs/deployment/com.google.santa.example.mobileconfig
Normal file
83
Docs/deployment/com.google.santa.example.mobileconfig
Normal 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<br/>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>
|
||||
@@ -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<br/>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<br/>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 |
|
||||
|
||||
@@ -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`.
|
||||
|
||||
36
Podfile.lock
36
Podfile.lock
@@ -1,15 +1,15 @@
|
||||
PODS:
|
||||
- FMDB (2.6.2):
|
||||
- FMDB/standard (= 2.6.2)
|
||||
- FMDB/standard (2.6.2)
|
||||
- MOLAuthenticatingURLSession (2.2):
|
||||
- MOLCertificate (~> 1.5)
|
||||
- MOLCertificate (1.5)
|
||||
- MOLCodesignChecker (1.5):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- MOLFCMClient (1.3):
|
||||
- MOLAuthenticatingURLSession (~> 2.1)
|
||||
- OCMock (3.4)
|
||||
- FMDB (2.7.2):
|
||||
- FMDB/standard (= 2.7.2)
|
||||
- FMDB/standard (2.7.2)
|
||||
- MOLAuthenticatingURLSession (2.4):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLCertificate (1.8)
|
||||
- MOLCodesignChecker (1.10):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLFCMClient (1.7):
|
||||
- MOLAuthenticatingURLSession (~> 2.4)
|
||||
- OCMock (3.4.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
@@ -20,13 +20,13 @@ DEPENDENCIES:
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
|
||||
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
|
||||
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
|
||||
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
|
||||
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
|
||||
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
|
||||
FMDB: 6198a90e7b6900cfc046e6bc0ef6ebb7be9236aa
|
||||
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
|
||||
MOLCertificate: c999513316d511c69f290fbf313dfe8dca4ad592
|
||||
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
|
||||
MOLFCMClient: ee45348909351f232e2759c580329072ae7e02d4
|
||||
OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3
|
||||
|
||||
PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe
|
||||
|
||||
COCOAPODS: 1.2.1
|
||||
COCOAPODS: 1.4.0
|
||||
|
||||
15
README.md
15
README.md
@@ -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
|
||||
|
||||
1
Rakefile
1
Rakefile
@@ -106,6 +106,7 @@ namespace :install do
|
||||
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
|
||||
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
|
||||
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
|
||||
system 'sudo cp conf/com.google.santa.newsyslog.conf /etc/newsyslog.d/'
|
||||
system '/usr/bin/killall -HUP syslogd'
|
||||
Rake::Task['build:build'].invoke(config)
|
||||
puts "Installing with configuration: #{config}"
|
||||
|
||||
@@ -72,8 +72,6 @@
|
||||
0D4644C6182AF81700098690 /* SantaDecisionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D4644C4182AF81700098690 /* SantaDecisionManager.h */; };
|
||||
0D536ED71B8E7A2E0039A26D /* bad_pagezero in Resources */ = {isa = PBXBuildFile; fileRef = 0D536ED51B8E7A2E0039A26D /* bad_pagezero */; };
|
||||
0D536ED81B8E7A2E0039A26D /* missing_pagezero in Resources */ = {isa = PBXBuildFile; fileRef = 0D536ED61B8E7A2E0039A26D /* missing_pagezero */; };
|
||||
0D536EDB1B94E9230039A26D /* SNTEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D536EDA1B94E9230039A26D /* SNTEventLog.m */; };
|
||||
0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D536EDA1B94E9230039A26D /* SNTEventLog.m */; };
|
||||
0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */; };
|
||||
0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */; };
|
||||
0D668E8118D1121700E29A8B /* SNTMessageWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D668E8018D1121700E29A8B /* SNTMessageWindow.m */; };
|
||||
@@ -148,10 +146,6 @@
|
||||
0DEA5F7B1CF64C9200704398 /* sync_ruledownload_batch2.json in Resources */ = {isa = PBXBuildFile; fileRef = 0DEA5F781CF64C8B00704398 /* sync_ruledownload_batch2.json */; };
|
||||
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 */; };
|
||||
0DF395661AB76ABC00CBC520 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DF395651AB76ABC00CBC520 /* libz.dylib */; };
|
||||
168A4E09A2A5E0B7DF8A2F1A /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C11A10A5D6E112788769CF70 /* libPods-santad.a */; };
|
||||
@@ -168,6 +162,12 @@
|
||||
C7479F051E53704E0054C1CF /* SNTXPCBundleServiceInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7C721B01E23FF300051FAA6 /* SNTXPCBundleServiceInterface.m */; };
|
||||
C7479F071E5374BF0054C1CF /* SNTXPCControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */; };
|
||||
C7479F091E5374E50054C1CF /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; };
|
||||
C748E8A3206964E1006CFD1B /* SNTEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = C748E8A2206964DE006CFD1B /* SNTEventLog.m */; };
|
||||
C748E8A4206964EE006CFD1B /* SNTEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = C748E8A2206964DE006CFD1B /* SNTEventLog.m */; };
|
||||
C748E8A720696595006CFD1B /* SNTFileEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = C748E8A620696595006CFD1B /* SNTFileEventLog.m */; };
|
||||
C748E8A820696595006CFD1B /* SNTFileEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = C748E8A620696595006CFD1B /* SNTFileEventLog.m */; };
|
||||
C748E8B020697F01006CFD1B /* SNTSyslogEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = C748E8AF20697F01006CFD1B /* SNTSyslogEventLog.m */; };
|
||||
C748E8B120697F01006CFD1B /* SNTSyslogEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = C748E8AF20697F01006CFD1B /* SNTSyslogEventLog.m */; };
|
||||
C74D6CC61EEB3B9B00BB5A33 /* BundleExample.app in Resources */ = {isa = PBXBuildFile; fileRef = C74D6CC51EEB3B9B00BB5A33 /* BundleExample.app */; };
|
||||
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
|
||||
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */; };
|
||||
@@ -345,8 +345,6 @@
|
||||
0D5058CF1CB70123008784BA /* SNTStrengthify.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTStrengthify.h; sourceTree = "<group>"; };
|
||||
0D536ED51B8E7A2E0039A26D /* bad_pagezero */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = bad_pagezero; sourceTree = "<group>"; };
|
||||
0D536ED61B8E7A2E0039A26D /* missing_pagezero */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = missing_pagezero; sourceTree = "<group>"; };
|
||||
0D536ED91B94E9230039A26D /* SNTEventLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTEventLog.h; sourceTree = "<group>"; };
|
||||
0D536EDA1B94E9230039A26D /* SNTEventLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTEventLog.m; sourceTree = "<group>"; };
|
||||
0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDatabaseController.h; sourceTree = "<group>"; };
|
||||
0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDatabaseController.m; sourceTree = "<group>"; };
|
||||
0D668E7F18D1121700E29A8B /* SNTMessageWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTMessageWindow.h; sourceTree = "<group>"; };
|
||||
@@ -415,9 +413,6 @@
|
||||
0DEA5F781CF64C8B00704398 /* sync_ruledownload_batch2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sync_ruledownload_batch2.json; sourceTree = "<group>"; };
|
||||
0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncConstants.m; sourceTree = "<group>"; };
|
||||
0DEFB7C11ACB28BC00B92AAE /* SNTCommandSyncConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncConstants.h; sourceTree = "<group>"; };
|
||||
0DEFB7C21ACDD80100B92AAE /* SNTFileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTFileWatcher.h; sourceTree = "<group>"; };
|
||||
0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTFileWatcher.m; sourceTree = "<group>"; };
|
||||
0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTFileWatcherTest.m; sourceTree = "<group>"; };
|
||||
0DF395621AB76A7900CBC520 /* NSData+Zlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Zlib.h"; sourceTree = "<group>"; };
|
||||
0DF395631AB76A7900CBC520 /* NSData+Zlib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Zlib.m"; sourceTree = "<group>"; };
|
||||
0DF395651AB76ABC00CBC520 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
|
||||
@@ -436,6 +431,12 @@
|
||||
A6A91785C40257CC156B4F05 /* 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>"; };
|
||||
C11A10A5D6E112788769CF70 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFileInfoTest.m; sourceTree = "<group>"; };
|
||||
C748E8A1206964DE006CFD1B /* SNTEventLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTEventLog.h; sourceTree = "<group>"; };
|
||||
C748E8A2206964DE006CFD1B /* SNTEventLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTEventLog.m; sourceTree = "<group>"; };
|
||||
C748E8A520696594006CFD1B /* SNTFileEventLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTFileEventLog.h; sourceTree = "<group>"; };
|
||||
C748E8A620696595006CFD1B /* SNTFileEventLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTFileEventLog.m; sourceTree = "<group>"; };
|
||||
C748E8AE20697F01006CFD1B /* SNTSyslogEventLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTSyslogEventLog.h; sourceTree = "<group>"; };
|
||||
C748E8AF20697F01006CFD1B /* SNTSyslogEventLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTSyslogEventLog.m; sourceTree = "<group>"; };
|
||||
C74D6CC51EEB3B9B00BB5A33 /* BundleExample.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = BundleExample.app; 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>"; };
|
||||
@@ -539,7 +540,6 @@
|
||||
0D41DAD31A7C28C800A890FE /* SNTEventTableTest.m */,
|
||||
0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */,
|
||||
0DD0D48E194F78F8005F27EB /* SNTFileInfoTest.m */,
|
||||
0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */,
|
||||
0DB537861AFD36EB00487F92 /* SNTRuleTableTest.m */,
|
||||
0D3AFBE618FB32CB0087BCEE /* SNTXPCConnectionTest.m */,
|
||||
);
|
||||
@@ -732,8 +732,6 @@
|
||||
0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */,
|
||||
0DCD6040190ACCB8006B445C /* SNTFileInfo.h */,
|
||||
0DCD6041190ACCB8006B445C /* SNTFileInfo.m */,
|
||||
0DEFB7C21ACDD80100B92AAE /* SNTFileWatcher.h */,
|
||||
0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */,
|
||||
0D28E5E31926AFE400280F87 /* SNTKernelCommon.h */,
|
||||
0D28E5E119269B3600280F87 /* SNTLogging.h */,
|
||||
0DA73C9E1934F8100056D7C4 /* SNTLogging.m */,
|
||||
@@ -762,6 +760,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0DA73CA519363C9F0056D7C4 /* DataLayer */,
|
||||
C748E8A020696001006CFD1B /* Logs */,
|
||||
0D9A7F411759330500035EB5 /* main.m */,
|
||||
0DB8ACBF185662DC00FEF9C7 /* SNTApplication.h */,
|
||||
0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */,
|
||||
@@ -775,8 +774,6 @@
|
||||
C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */,
|
||||
0D7D01851774F93A005DBAB4 /* SNTDriverManager.h */,
|
||||
0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */,
|
||||
0D536ED91B94E9230039A26D /* SNTEventLog.h */,
|
||||
0D536EDA1B94E9230039A26D /* SNTEventLog.m */,
|
||||
0DE6788B1784A8C2007A9E52 /* SNTExecutionController.h */,
|
||||
0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */,
|
||||
0DE5B5491C926E3300C00603 /* SNTNotificationQueue.h */,
|
||||
@@ -850,6 +847,19 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C748E8A020696001006CFD1B /* Logs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C748E8A1206964DE006CFD1B /* SNTEventLog.h */,
|
||||
C748E8A2206964DE006CFD1B /* SNTEventLog.m */,
|
||||
C748E8AE20697F01006CFD1B /* SNTSyslogEventLog.h */,
|
||||
C748E8AF20697F01006CFD1B /* SNTSyslogEventLog.m */,
|
||||
C748E8A520696594006CFD1B /* SNTFileEventLog.h */,
|
||||
C748E8A620696595006CFD1B /* SNTFileEventLog.m */,
|
||||
);
|
||||
path = Logs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C79A23541E23F7E80037AFA8 /* santabs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1221,13 +1231,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 +1249,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 +1312,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 +1330,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 +1380,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 */ = {
|
||||
@@ -1432,8 +1457,9 @@
|
||||
C7DA62F71E241938009BDF2C /* SNTXPCBundleServiceInterface.m in Sources */,
|
||||
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */,
|
||||
0D88680C1AC48A1400B86659 /* SNTSystemInfo.m in Sources */,
|
||||
0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */,
|
||||
0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */,
|
||||
C748E8A4206964EE006CFD1B /* SNTEventLog.m in Sources */,
|
||||
C748E8B120697F01006CFD1B /* SNTSyslogEventLog.m in Sources */,
|
||||
C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */,
|
||||
0DB77FDB1CD14093004DF060 /* SNTBlockMessage.m in Sources */,
|
||||
0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */,
|
||||
@@ -1450,10 +1476,9 @@
|
||||
0DCD605819115E57006B445C /* SNTXPCControlInterface.m in Sources */,
|
||||
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 */,
|
||||
C748E8A820696595006CFD1B /* SNTFileEventLog.m in Sources */,
|
||||
0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */,
|
||||
0DE5B54C1C92722300C00603 /* SNTNotificationQueue.m in Sources */,
|
||||
0DEA5F651CF6057D00704398 /* SNTCommandSyncEventUpload.m in Sources */,
|
||||
@@ -1532,7 +1557,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 */,
|
||||
@@ -1556,6 +1580,7 @@
|
||||
0D8868091AC48A1100B86659 /* SNTSystemInfo.m in Sources */,
|
||||
0DE6788D1784A8C2007A9E52 /* SNTExecutionController.m in Sources */,
|
||||
0D10BE861A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */,
|
||||
C748E8B020697F01006CFD1B /* SNTSyslogEventLog.m in Sources */,
|
||||
0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */,
|
||||
0DCD604B19105433006B445C /* SNTStoredEvent.m in Sources */,
|
||||
C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */,
|
||||
@@ -1566,7 +1591,6 @@
|
||||
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 */,
|
||||
0D7D01871774F93A005DBAB4 /* SNTDriverManager.m in Sources */,
|
||||
0D8E18CD19107B56000F89B8 /* SNTDaemonControlController.m in Sources */,
|
||||
@@ -1575,10 +1599,11 @@
|
||||
0DE50F681912716A007B2B0C /* SNTRule.m in Sources */,
|
||||
0DB77FD81CCE824A004DF060 /* SNTBlockMessage.m in Sources */,
|
||||
0D37C10F18F6029A0069BC61 /* SNTDatabaseTable.m in Sources */,
|
||||
C748E8A720696595006CFD1B /* SNTFileEventLog.m in Sources */,
|
||||
C748E8A3206964E1006CFD1B /* SNTEventLog.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 */,
|
||||
);
|
||||
@@ -2025,6 +2050,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ENABLE_CODE_COVERAGE = NO;
|
||||
CLANG_ENABLE_MODULES = NO;
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
@@ -2063,6 +2089,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ENABLE_CODE_COVERAGE = NO;
|
||||
CLANG_ENABLE_MODULES = NO;
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
|
||||
@@ -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:kDefaultConfigFilePath
|
||||
handler:^(unsigned long data) {
|
||||
if (! (data & DISPATCH_VNODE_ATTRIB)) [[SNTConfigurator configurator] reloadConfigData];
|
||||
}];
|
||||
|
||||
self.notificationManager = [[SNTNotificationManager alloc] init];
|
||||
|
||||
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
@import SecurityInterface.SFCertificatePanel;
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "SNTBlockMessage.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTMessageWindow.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -80,6 +80,12 @@ typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
|
||||
SNTBundleEventActionSendEvents,
|
||||
};
|
||||
|
||||
// Indicates where to store event logs.
|
||||
typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santad";
|
||||
static const char *kSantaCtlPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl";
|
||||
|
||||
@@ -19,27 +19,21 @@
|
||||
///
|
||||
/// 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
|
||||
|
||||
/// Default config file path
|
||||
extern NSString *const kDefaultConfigFilePath;
|
||||
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// 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 kDefaultConfigFilePath;
|
||||
/// 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 kDefaultConfigFilePath;
|
||||
/// 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
|
||||
@@ -66,6 +79,25 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enablePageZeroProtection;
|
||||
|
||||
///
|
||||
/// Defines how event logs are stored. Options are:
|
||||
/// SNTEventLogTypeSyslog: Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog: Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTEventLogType eventLogType;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to Filelog, eventLogPath will provide the path to save logs.
|
||||
/// Defaults to /var/db/santa/santa.log.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
///
|
||||
@@ -202,15 +234,8 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
+ (instancetype)configurator;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
/// Clear the sync server configuration from the effective configuration.
|
||||
///
|
||||
/// @param filePath The path to the file to use as a backing store.
|
||||
///
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath;
|
||||
|
||||
///
|
||||
/// Re-read config data from disk.
|
||||
///
|
||||
- (void)reloadConfigData;
|
||||
- (void)clearSyncState;
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,46 +14,35 @@
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStrengthify.h"
|
||||
#import "SNTSystemInfo.h"
|
||||
|
||||
@interface SNTConfigurator ()
|
||||
@property NSString *configFilePath;
|
||||
@property NSMutableDictionary *configData;
|
||||
/// A NSUserDefaults object set to use the com.google.santa suite.
|
||||
@property(readonly, nonatomic) NSUserDefaults *defaults;
|
||||
|
||||
/// Creating NSRegularExpression objects is not fast, so cache them.
|
||||
@property NSRegularExpression *cachedFileChangesRegex;
|
||||
@property NSRegularExpression *cachedWhitelistDirRegex;
|
||||
@property NSRegularExpression *cachedBlacklistDirRegex;
|
||||
// Keys and expected value types.
|
||||
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
|
||||
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
|
||||
|
||||
/// Array of keys that cannot be changed while santad is running if santad didn't make the change.
|
||||
@property(readonly) NSArray *protectedKeys;
|
||||
/// Holds the configurations from a sync server and mobileconfig.
|
||||
@property NSMutableDictionary *syncState;
|
||||
@property NSMutableDictionary *configState;
|
||||
@end
|
||||
|
||||
@implementation SNTConfigurator
|
||||
|
||||
/// The hard-coded path to the config file
|
||||
NSString *const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
/// The hard-coded path to the sync state file.
|
||||
NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
|
||||
|
||||
/// The keys in the config file
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
|
||||
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
|
||||
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
|
||||
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
/// The domain used by mobileconfig.
|
||||
static NSString *const kMobileConfigDomain = @"com.google.santa";
|
||||
|
||||
/// The keys managed by a mobileconfig.
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
@@ -63,18 +52,86 @@ static NSString *const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
|
||||
|
||||
static NSString *const kMachineOwnerKey = @"MachineOwner";
|
||||
static NSString *const kMachineIDKey = @"MachineID";
|
||||
|
||||
static NSString *const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
|
||||
static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
|
||||
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath {
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
|
||||
|
||||
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
|
||||
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
static NSString *const kEventLogPath = @"EventLogPath";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_configFilePath = filePath;
|
||||
[self reloadConfigData];
|
||||
Class number = [NSNumber class];
|
||||
Class re = [NSRegularExpression class];
|
||||
Class date = [NSDate class];
|
||||
Class string = [NSString class];
|
||||
Class data = [NSData class];
|
||||
_syncServerKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kWhitelistRegexKey : re,
|
||||
kBlacklistRegexKey : re,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kFileChangesRegexKey : re,
|
||||
kWhitelistRegexKey : re,
|
||||
kBlacklistRegexKey : re,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kSyncBaseURLKey : string,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
kMachineIDKey : string,
|
||||
kMachineOwnerPlistFileKey : string,
|
||||
kMachineOwnerPlistKeyKey : string,
|
||||
kMachineIDPlistFileKey : string,
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
_configState = [self readForcedConfig];
|
||||
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
|
||||
[self startWatchingDefaults];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -82,310 +139,422 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
#pragma mark Singleton retriever
|
||||
|
||||
+ (instancetype)configurator {
|
||||
static SNTConfigurator *sharedConfigurator = nil;
|
||||
static SNTConfigurator *sharedConfigurator;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
|
||||
sharedConfigurator = [[SNTConfigurator alloc] init];
|
||||
});
|
||||
return sharedConfigurator;
|
||||
}
|
||||
|
||||
#pragma mark Protected Keys
|
||||
+ (NSSet *)syncAndConfigStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [[self syncStateSet] setByAddingObjectsFromSet:[self configStateSet]];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
- (NSArray *)protectedKeys {
|
||||
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey,
|
||||
kFileChangesRegexKey, kSyncBaseURLKey ];
|
||||
+ (NSSet *)syncStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithObject:NSStringFromSelector(@selector(syncState))];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
+ (NSSet *)configStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithObject:NSStringFromSelector(@selector(configState))];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
#pragma mark KVO Dependencies
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingClientMode {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileChangesRegex {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventDetailURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventDetailText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBannedBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingModeNotificationMonitor {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateFile {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificatePassword {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateCn {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateIssuer {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsData {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsFile {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMachineOwner {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMachineID {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRuleSyncLastSuccess {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncCleanRequired {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventLogType {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventLogPath {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
#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 saveConfigToDisk];
|
||||
[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 saveConfigToDisk];
|
||||
- (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 saveConfigToDisk];
|
||||
- (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 saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (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 saveConfigToDisk];
|
||||
[self updateSyncStateForKey:kFullSyncLastSuccess value:fullSyncLastSuccess];
|
||||
self.ruleSyncLastSuccess = fullSyncLastSuccess;
|
||||
}
|
||||
|
||||
- (NSDate *)ruleSyncLastSuccess {
|
||||
return self.configData[kRuleSyncLastSuccess];
|
||||
return self.syncState[kRuleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
|
||||
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
|
||||
[self saveConfigToDisk];
|
||||
[self updateSyncStateForKey:kRuleSyncLastSuccess value:ruleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (BOOL)syncCleanRequired {
|
||||
return [self.configData[kSyncCleanRequired] boolValue];
|
||||
return [self.syncState[kSyncCleanRequired] boolValue];
|
||||
}
|
||||
|
||||
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
|
||||
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
|
||||
[self saveConfigToDisk];
|
||||
[self updateSyncStateForKey:kSyncCleanRequired value:@(syncCleanRequired)];
|
||||
}
|
||||
|
||||
- (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;
|
||||
return machineId.length ? machineId : [SNTSystemInfo hardwareUUID];
|
||||
}
|
||||
|
||||
- (void)reloadConfigData {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:self.configFilePath]) {
|
||||
// As soon as saveConfigToDisk is called, reloadConfigData will be called again because
|
||||
// of the SNTFileWatchers on the config path. No need to use dictionaryWithCapacity: here.
|
||||
self.configData = [NSMutableDictionary dictionary];
|
||||
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
|
||||
[self saveConfigToDisk];
|
||||
return;
|
||||
};
|
||||
- (SNTEventLogType)eventLogType {
|
||||
NSString *s = [self.configState[kEventLogType] lowercaseString];
|
||||
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not read configuration file: %@, replacing.", [error localizedDescription]);
|
||||
[self saveConfigToDisk];
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *configData =
|
||||
[NSPropertyListSerialization propertyListWithData:readData
|
||||
options:NSPropertyListMutableContainers
|
||||
format:NULL
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not parse configuration file: %@, replacing.", [error localizedDescription]);
|
||||
[self saveConfigToDisk];
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.syncBaseURL) {
|
||||
// Ensure no-one is trying to change protected keys behind our back.
|
||||
BOOL changed = NO;
|
||||
if (geteuid() == 0) {
|
||||
for (NSString *key in self.protectedKeys) {
|
||||
if (((self.configData[key] && !configData[key]) ||
|
||||
(!self.configData[key] && configData[key]) ||
|
||||
(self.configData[key] && ![self.configData[key] isEqual:configData[key]]))) {
|
||||
if (self.configData[key]) {
|
||||
configData[key] = self.configData[key];
|
||||
} else {
|
||||
[configData removeObjectForKey:key];
|
||||
}
|
||||
changed = YES;
|
||||
LOGI(@"Ignoring changed configuration key: %@", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.configData = configData;
|
||||
if (changed) [self saveConfigToDisk];
|
||||
} else {
|
||||
self.configData = configData;
|
||||
}
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
///
|
||||
/// Saves the current @c self.configData to disk.
|
||||
/// Update the syncState. Triggers a KVO event for all dependents.
|
||||
///
|
||||
- (void)saveConfigToDisk {
|
||||
- (void)updateSyncStateForKey:(NSString *)key value:(id)value {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
syncState[key] = value;
|
||||
self.syncState = syncState;
|
||||
[self saveSyncStateToDisk];
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
/// 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 =
|
||||
[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;
|
||||
}
|
||||
}
|
||||
return syncState;
|
||||
}
|
||||
|
||||
///
|
||||
/// 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;
|
||||
[self.configData writeToFile:self.configFilePath atomically:YES];
|
||||
// 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];
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -212,6 +212,11 @@
|
||||
///
|
||||
- (NSUInteger)fileSize;
|
||||
|
||||
///
|
||||
/// @return The underlying file handle.
|
||||
///
|
||||
@property(readonly) NSFileHandle *fileHandle;
|
||||
|
||||
///
|
||||
/// @return Returns an instance of MOLCodeSignChecker initialized with the file's binary path.
|
||||
/// Both the MOLCodesignChecker and any resulting NSError are cached and returned on subsequent
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <pwd.h>
|
||||
@@ -287,7 +288,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
- (BOOL)isMissingPageZero {
|
||||
// This method only checks i386 arch because the kernel enforces this for other archs
|
||||
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
|
||||
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86]];
|
||||
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86
|
||||
cpuSubType:CPU_SUBTYPE_I386_ALL]];
|
||||
if (!x86Header) return NO;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
|
||||
@@ -443,7 +445,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
if (machHeader) {
|
||||
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
|
||||
machHeaders[[self nameForCPUType:mh->cputype]] = mhwo;
|
||||
machHeaders[[self nameForCPUType:mh->cputype cpuSubType:mh->cpusubtype]] = mhwo;
|
||||
} else {
|
||||
NSRange range = NSMakeRange(0, sizeof(struct fat_header));
|
||||
NSData *fatHeader = [self safeSubdataWithRange:range];
|
||||
@@ -459,11 +461,12 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
int offset = OSSwapBigToHostInt32(fat_arch[i].offset);
|
||||
int size = OSSwapBigToHostInt32(fat_arch[i].size);
|
||||
int cputype = OSSwapBigToHostInt(fat_arch[i].cputype);
|
||||
int cpusubtype = OSSwapBigToHostInt(fat_arch[i].cpusubtype);
|
||||
|
||||
range = NSMakeRange(offset, size);
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:range]];
|
||||
if (machHeader) {
|
||||
NSString *key = [self nameForCPUType:cputype];
|
||||
NSString *key = [self nameForCPUType:cputype cpuSubType:cpusubtype];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader
|
||||
offset:offset];
|
||||
machHeaders[key] = mhwo;
|
||||
@@ -647,20 +650,15 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
///
|
||||
/// Return a human-readable string for a cpu_type_t.
|
||||
///
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
|
||||
switch (cpuType) {
|
||||
case CPU_TYPE_X86:
|
||||
return @"i386";
|
||||
case CPU_TYPE_X86_64:
|
||||
return @"x86-64";
|
||||
case CPU_TYPE_POWERPC:
|
||||
return @"ppc";
|
||||
case CPU_TYPE_POWERPC64:
|
||||
return @"ppc64";
|
||||
default:
|
||||
return @"unknown";
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType cpuSubType:(cpu_subtype_t)cpuSubType {
|
||||
const NXArchInfo *archInfo = NXGetArchInfoFromCpuType(cpuType, cpuSubType);
|
||||
NSString *arch;
|
||||
if (archInfo && archInfo->name) {
|
||||
arch = @(archInfo->name);
|
||||
} else {
|
||||
arch = [NSString stringWithFormat:@"%i:%i", cpuType, cpuSubType];
|
||||
}
|
||||
return nil;
|
||||
return arch;
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@import Foundation;
|
||||
|
||||
///
|
||||
/// Simple file watching class using dispatch sources. Will automatically
|
||||
/// reload the watch if the file is deleted and continue watching for
|
||||
/// events until deallocated.
|
||||
///
|
||||
@interface SNTFileWatcher : NSObject
|
||||
|
||||
///
|
||||
/// Designated initializer
|
||||
/// Initializes the watcher and begins watching for modifications.
|
||||
///
|
||||
/// @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.
|
||||
///
|
||||
- (nonnull instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
handler:(nonnull void (^)(unsigned long))handler;
|
||||
|
||||
@end
|
||||
@@ -1,94 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTFileWatcher.h"
|
||||
|
||||
#import "SNTStrengthify.h"
|
||||
|
||||
@interface SNTFileWatcher ()
|
||||
@property NSString *filePath;
|
||||
@property(copy) void (^handler)(unsigned long);
|
||||
|
||||
@property dispatch_source_t source;
|
||||
@end
|
||||
|
||||
@implementation SNTFileWatcher
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
handler:(nonnull void (^)(unsigned long))handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_handler = handler;
|
||||
[self startWatchingFile];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopWatchingFile];
|
||||
}
|
||||
|
||||
- (void)startWatchingFile {
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
||||
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME |
|
||||
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB);
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
int fd = -1;
|
||||
const char *filePath = [self.filePath fileSystemRepresentation];
|
||||
while ((fd = open(filePath, O_EVTONLY | O_CLOEXEC)) < 0) {
|
||||
usleep(200000); // wait 200ms
|
||||
}
|
||||
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
|
||||
|
||||
WEAKIFY(self);
|
||||
|
||||
dispatch_source_set_event_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
unsigned long data = dispatch_source_get_data(self.source);
|
||||
self.handler(data);
|
||||
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
|
||||
[self stopWatchingFile];
|
||||
[self startWatchingFile];
|
||||
}
|
||||
sleep(2);
|
||||
});
|
||||
|
||||
dispatch_source_set_registration_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
self.handler(0);
|
||||
});
|
||||
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
close(fd);
|
||||
});
|
||||
|
||||
dispatch_resume(self.source);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopWatchingFile {
|
||||
if (!self.source) return;
|
||||
dispatch_source_set_event_handler_f(self.source, NULL);
|
||||
dispatch_source_cancel(self.source);
|
||||
self.source = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -37,6 +37,7 @@ enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
kSantaUserClientAllowBinary,
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientAcknowledgeBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientCacheCount,
|
||||
kSantaUserClientCheckCache,
|
||||
@@ -63,6 +64,8 @@ typedef enum {
|
||||
// RESPONSES
|
||||
ACTION_RESPOND_ALLOW = 20,
|
||||
ACTION_RESPOND_DENY = 21,
|
||||
ACTION_RESPOND_TOOLONG = 22,
|
||||
ACTION_RESPOND_ACK = 23,
|
||||
|
||||
// NOTIFY
|
||||
ACTION_NOTIFY_EXEC = 30,
|
||||
|
||||
@@ -29,12 +29,12 @@ void syslogClientDestructor(void *arg) {
|
||||
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static const char *binaryName;
|
||||
static NSString *binaryName;
|
||||
static dispatch_once_t pred;
|
||||
static pthread_key_t syslogKey = 0;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
binaryName = [[[NSProcessInfo processInfo] processName] UTF8String];
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
@@ -76,7 +76,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
break;
|
||||
case LOG_LEVEL_INFO:
|
||||
levelName = "I";
|
||||
syslogLevel = ASL_LEVEL_INFO;
|
||||
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
|
||||
break;
|
||||
case LOG_LEVEL_DEBUG:
|
||||
levelName = "D";
|
||||
@@ -84,7 +84,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
break;
|
||||
}
|
||||
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName.UTF8String, s.UTF8String);
|
||||
} else {
|
||||
[s appendString:@"\n"];
|
||||
size_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
@implementation SNTStoredEvent
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "SNTStrengthify.h"
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_);
|
||||
@@ -366,8 +377,10 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
int name_len = MAXPATHLEN;
|
||||
if (vn_getpath(vp, path, &name_len) != 0) {
|
||||
path[0] = '\0';
|
||||
path[MAXPATHLEN - 1] = 0;
|
||||
|
||||
if (vn_getpath(vp, path, &name_len) == ENOSPC) {
|
||||
return ACTION_RESPOND_TOOLONG;
|
||||
}
|
||||
|
||||
auto message = NewMessage(cred);
|
||||
@@ -463,6 +476,9 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
case ACTION_RESPOND_DENY:
|
||||
*errno = EPERM;
|
||||
return KAUTH_RESULT_DENY;
|
||||
case ACTION_RESPOND_TOOLONG:
|
||||
*errno = ENAMETOOLONG;
|
||||
return KAUTH_RESULT_DENY;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
|
||||
@@ -79,8 +79,9 @@ class SantaDecisionManager : public OSObject {
|
||||
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();
|
||||
|
||||
@@ -89,7 +90,10 @@ class SantaDecisionManager : public OSObject {
|
||||
const santa_action_t decision,
|
||||
const uint64_t microsecs = GetCurrentUptime());
|
||||
|
||||
/// Fetches a response from the cache, first checking to see if the entry has expired.
|
||||
/**
|
||||
Fetches a response from the cache, first checking to see if the entry
|
||||
has expired.
|
||||
*/
|
||||
santa_action_t GetFromCache(uint64_t identifier);
|
||||
|
||||
/// Checks to see if a given identifier is in the cache and removes it.
|
||||
@@ -99,7 +103,10 @@ class SantaDecisionManager : public OSObject {
|
||||
uint64_t RootCacheCount() const;
|
||||
uint64_t NonRootCacheCount() const;
|
||||
|
||||
/// Clears the cache(s). If non_root_only is true, only the non-root cache is cleared.
|
||||
/**
|
||||
Clears the cache(s). If non_root_only is true, only the non-root cache
|
||||
is cleared.
|
||||
*/
|
||||
void ClearCache(bool non_root_only = false);
|
||||
|
||||
/// Increments the count of active callbacks pending.
|
||||
@@ -137,7 +144,16 @@ class SantaDecisionManager : public OSObject {
|
||||
*/
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
|
||||
|
||||
/// 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 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.
|
||||
*/
|
||||
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
@@ -146,10 +162,16 @@ class SantaDecisionManager : public OSObject {
|
||||
/// 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 that can be kept in the decision data queue
|
||||
at any time.
|
||||
*/
|
||||
static const uint32_t kMaxDecisionQueueEvents = 512;
|
||||
|
||||
/// The maximum number of messages can be kept in the logging data queue at any time.
|
||||
/**
|
||||
The maximum number of messages that can be kept in the logging data queue
|
||||
at any time.
|
||||
*/
|
||||
static const uint32_t kMaxLogQueueEvents = 2048;
|
||||
|
||||
/**
|
||||
@@ -199,7 +221,8 @@ class SantaDecisionManager : public OSObject {
|
||||
@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) {
|
||||
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);
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <pthread/pthread.h>
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
@@ -72,18 +72,18 @@
|
||||
}
|
||||
|
||||
- (void)attemptReconnection {
|
||||
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
|
||||
[self performSelectorOnMainThread:@selector(createConnection) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
#pragma mark SNTBundleServiceXPC Methods
|
||||
|
||||
// Connect to the SantaGUI
|
||||
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener {
|
||||
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
|
||||
c.remoteInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
|
||||
[c resume];
|
||||
self.notifierConnection = c;
|
||||
dispatch_async(self.queue, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
|
||||
c.remoteInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
|
||||
[c resume];
|
||||
self.notifierConnection = c;
|
||||
[self createConnection];
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -42,6 +42,7 @@ static NSString *const kPageZero = @"Page Zero";
|
||||
static NSString *const kCodeSigned = @"Code-signed";
|
||||
static NSString *const kRule = @"Rule";
|
||||
static NSString *const kSigningChain = @"Signing Chain";
|
||||
static NSString *const kUniversalSigningChain = @"Universal Signing Chain";
|
||||
|
||||
// signing chain keys
|
||||
static NSString *const kCommonName = @"Common Name";
|
||||
@@ -115,6 +116,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock rule;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock signingChain;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock universalSigningChain;
|
||||
|
||||
// Mapping between property string keys and SNTAttributeBlocks
|
||||
@property(nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
|
||||
@@ -182,7 +184,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
+ (NSArray<NSString *> *)fileInfoKeys {
|
||||
return @[ kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr,
|
||||
kDownloadReferrerURL, kDownloadURL, kDownloadTimestamp, kDownloadAgent,
|
||||
kType, kPageZero, kCodeSigned, kRule, kSigningChain ];
|
||||
kType, kPageZero, kCodeSigned, kRule, kSigningChain, kUniversalSigningChain ];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)signingChainKeys {
|
||||
@@ -210,7 +212,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
kPageZero : self.pageZero,
|
||||
kCodeSigned : self.codeSigned,
|
||||
kRule : self.rule,
|
||||
kSigningChain : self.signingChain };
|
||||
kSigningChain : self.signingChain,
|
||||
kUniversalSigningChain : self.universalSigningChain };
|
||||
|
||||
_printQueue = dispatch_queue_create("com.google.santactl.print_queue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
@@ -325,6 +328,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
return @"Yes, but failed requirement validation";
|
||||
case errSecCSInfoPlistFailed:
|
||||
return @"Yes, but can't validate as Info.plist is missing";
|
||||
case errSecCSSignatureInvalid:
|
||||
if ([error.domain isEqualToString:@"com.google.molcodesignchecker"]) {
|
||||
return @"Yes, but signing is not consistent for all architectures";
|
||||
}
|
||||
default: {
|
||||
return [NSString stringWithFormat:@"Yes, but failed to validate (%ld)", error.code];
|
||||
}
|
||||
@@ -345,10 +352,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);
|
||||
@@ -417,6 +425,46 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)universalSigningChain {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
|
||||
if (csc.certificates.count) return nil;
|
||||
if (!csc.universalSigningInformation) return nil;
|
||||
NSMutableArray *universal = [NSMutableArray array];
|
||||
for (NSDictionary *arch in csc.universalSigningInformation) {
|
||||
[universal addObject:@{ @"arch" : arch.allKeys.firstObject }];
|
||||
int flags = [arch.allValues.firstObject[(__bridge id)kSecCodeInfoFlags] intValue];
|
||||
if (flags & kSecCodeSignatureAdhoc) {
|
||||
[universal addObject:@{ @"ad-hoc" : @YES }];
|
||||
continue;
|
||||
}
|
||||
NSArray *certs = arch.allValues.firstObject[(__bridge id)kSecCodeInfoCertificates];
|
||||
NSArray *chain = [MOLCertificate certificatesFromArray:certs];
|
||||
if (!chain.count) {
|
||||
[universal addObject:@{ @"unsigned" : @YES }];
|
||||
continue;
|
||||
}
|
||||
for (MOLCertificate *c in chain) {
|
||||
[universal addObject:@{
|
||||
kSHA256 : c.SHA256 ?: @"null",
|
||||
kSHA1 : c.SHA1 ?: @"null",
|
||||
kCommonName : c.commonName ?: @"null",
|
||||
kOrganization : c.orgName ?: @"null",
|
||||
kOrganizationalUnit : c.orgUnit ?: @"null",
|
||||
kValidFrom : [cmd.dateFormatter stringFromDate:c.validFrom] ?: @"null",
|
||||
kValidUntil : [cmd.dateFormatter stringFromDate:c.validUntil] ?: @"null"
|
||||
}];
|
||||
}
|
||||
}
|
||||
NSMutableSet *set = [NSMutableSet set];
|
||||
for (NSDictionary *cert in universal) {
|
||||
if (cert[@"arch"]) continue;
|
||||
[set addObject:cert];
|
||||
}
|
||||
return (set.count > 1) ? universal : nil;
|
||||
};
|
||||
}
|
||||
|
||||
# pragma mark -
|
||||
|
||||
// Entry point for the command.
|
||||
@@ -590,8 +638,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
} else {
|
||||
for (NSString *key in self.outputKeyList) {
|
||||
if (![outputDict objectForKey:key]) continue;
|
||||
if ([key isEqual:kSigningChain]) {
|
||||
[output appendString:[self stringForSigningChain:outputDict[key]]];
|
||||
if ([key isEqual:kSigningChain] || [key isEqual:kUniversalSigningChain]) {
|
||||
[output appendString:[self stringForSigningChain:outputDict[key] key:key]];
|
||||
} else {
|
||||
if (singleKey) {
|
||||
[output appendFormat:@"%@\n", outputDict[key]];
|
||||
@@ -728,14 +776,25 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (NSString *)stringForSigningChain:(NSArray *)signingChain {
|
||||
- (NSString *)stringForSigningChain:(NSArray *)signingChain key:(NSString *)key {
|
||||
if (!signingChain) return @"";
|
||||
NSMutableString *result = [NSMutableString string];
|
||||
[result appendFormat:@"%@:\n", kSigningChain];
|
||||
[result appendFormat:@"%@:\n", key];
|
||||
int i = 1;
|
||||
NSArray<NSString *> *certKeys = [[self class] signingChainKeys];
|
||||
for (NSDictionary *cert in signingChain) {
|
||||
if ([cert isEqual:[NSNull null]]) continue;
|
||||
if (cert[@"arch"]) {
|
||||
[result appendFormat:@" %2@\n", [@"Architecture: " stringByAppendingString:cert[@"arch"]]];
|
||||
i = 1;
|
||||
continue;
|
||||
} else if (cert[@"ad-hoc"]) {
|
||||
[result appendFormat:@" %2d. %-20@\n", i, @"ad-hoc"];
|
||||
continue;
|
||||
} else if (cert[@"unsigned"]) {
|
||||
[result appendFormat:@" %2d. %-20@\n", i, @"unsigned"];
|
||||
continue;
|
||||
}
|
||||
if (i > 1) [result appendFormat:@"\n"];
|
||||
[result appendString:[self stringForCertificate:cert withKeys:certKeys index:i]];
|
||||
i += 1;
|
||||
@@ -749,10 +808,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
BOOL firstKey = YES;
|
||||
for (NSString *key in keys) {
|
||||
if (firstKey) {
|
||||
[result appendFormat:@" %2d. %-20s: %@\n", index, key.UTF8String, cert[key]];
|
||||
[result appendFormat:@" %2d. %-20s: %@\n", index, key.UTF8String, cert[key]];
|
||||
firstKey = NO;
|
||||
} else {
|
||||
[result appendFormat:@" %-20s: %@\n", key.UTF8String, cert[key]];
|
||||
[result appendFormat:@" %-20s: %@\n", key.UTF8String, cert[key]];
|
||||
}
|
||||
}
|
||||
return result.copy;
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
#import "SNTCommand.h"
|
||||
#import "SNTCommandController.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTFileInfo.h"
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -131,6 +131,11 @@ static void reachabilityHandler(
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// Ensure reachability is always stopped
|
||||
[self stopReachability];
|
||||
}
|
||||
|
||||
#pragma mark SNTSyncdXPC protocol methods
|
||||
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle {
|
||||
@@ -173,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
|
||||
@@ -200,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];
|
||||
@@ -521,7 +527,8 @@ static void reachabilityHandler(
|
||||
const char *nodename = [[SNTConfigurator configurator] syncBaseURL].host.UTF8String;
|
||||
_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, nodename);
|
||||
SCNetworkReachabilityContext context = {
|
||||
.info = (__bridge void *)self
|
||||
.info = (__bridge_retained void *)self,
|
||||
.release = (void (*)(const void *))CFBridgingRelease,
|
||||
};
|
||||
if (SCNetworkReachabilitySetCallback(_reachability, reachabilityHandler, &context)) {
|
||||
SCNetworkReachabilitySetDispatchQueue(_reachability,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,9 @@ int main(int argc, const char *argv[]) {
|
||||
}
|
||||
[arguments removeObjectAtIndex:0];
|
||||
|
||||
if ([commandName isEqualToString:@"help"]) {
|
||||
if ([commandName isEqualToString:@"help"] ||
|
||||
[commandName isEqualToString:@"-h"] ||
|
||||
[commandName isEqualToString:@"--help"]) {
|
||||
if ([arguments count]) {
|
||||
// User wants help for specific command
|
||||
commandName = [arguments firstObject];
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
#import "SNTEventTable.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTEventTable
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
|
||||
#import "SNTRuleTable.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTRule.h"
|
||||
|
||||
54
Source/santad/Logs/SNTEventLog.h
Normal file
54
Source/santad/Logs/SNTEventLog.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@import Foundation;
|
||||
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
@class SNTCachedDecision;
|
||||
@class SNTStoredEvent;
|
||||
|
||||
///
|
||||
/// Logs execution and file write events to syslog
|
||||
///
|
||||
@interface SNTEventLog : NSObject
|
||||
// Methods implemented by a concrete subclass.
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties;
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties;
|
||||
- (void)logFileModification:(santa_message_t)message;
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message;
|
||||
- (void)logAllowedExecution:(santa_message_t)message;
|
||||
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events;
|
||||
|
||||
// Getter and setter for cached decisions.
|
||||
- (SNTCachedDecision *)cachedDecisionForMessage:(santa_message_t)message;
|
||||
- (void)cacheDecision:(SNTCachedDecision *)cd;
|
||||
|
||||
// String formatter helpers.
|
||||
- (void)addArgsForPid:(pid_t)pid toString:(NSMutableString *)str;
|
||||
- (NSString *)diskImageForDevice:(NSString *)devPath;
|
||||
- (NSString *)nameForUID:(uid_t)uid;
|
||||
- (NSString *)nameForGID:(gid_t)gid;
|
||||
- (NSString *)sanitizeCString:(const char *)str ofLength:(NSUInteger)length;
|
||||
- (NSString *)sanitizeString:(NSString *)inStr;
|
||||
- (NSString *)serialForDevice:(NSString *)devPath;
|
||||
- (NSString *)originalPathForTranslocation:(santa_message_t)message;
|
||||
|
||||
// A cache for usernames and groups.
|
||||
@property(readonly, nonatomic) NSCache<NSNumber *, NSString *> *userNameMap;
|
||||
@property(readonly, nonatomic) NSCache<NSNumber *, NSString *> *groupNameMap;
|
||||
|
||||
// A UTC Date formatter.
|
||||
@property(readonly, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2018 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.
|
||||
@@ -16,27 +16,14 @@
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <grp.h>
|
||||
#include <libproc.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#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<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;
|
||||
|
||||
@property NSDateFormatter *dateFormatter;
|
||||
@end
|
||||
|
||||
@implementation SNTEventLog
|
||||
@@ -60,210 +47,47 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)saveDecisionDetails:(SNTCachedDecision *)cd {
|
||||
dispatch_sync(_detailStoreQueue, ^{
|
||||
_detailStore[@(cd.vnodeId)] = cd;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message {
|
||||
NSString *action, *newpath;
|
||||
|
||||
NSString *path = @(message.path);
|
||||
|
||||
switch (message.action) {
|
||||
case ACTION_NOTIFY_DELETE: {
|
||||
action = @"DELETE";
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXCHANGE: {
|
||||
action = @"EXCHANGE";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_LINK: {
|
||||
action = @"LINK";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_RENAME: {
|
||||
action = @"RENAME";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
action = @"WRITE";
|
||||
break;
|
||||
}
|
||||
default: action = @"UNKNOWN"; break;
|
||||
}
|
||||
|
||||
// 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 appendFormat:@"|newpath=%@", [self sanitizeString:newpath]];
|
||||
}
|
||||
char ppath[PATH_MAX] = "(null)";
|
||||
proc_pidpath(message.pid, ppath, PATH_MAX);
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message {
|
||||
[self logExecution:message withDecision:cd];
|
||||
}
|
||||
|
||||
- (void)logAllowedExecution:(santa_message_t)message {
|
||||
__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;
|
||||
BOOL logArgs = NO;
|
||||
|
||||
switch (cd.decision) {
|
||||
case SNTEventStateAllowBinary:
|
||||
d = @"ALLOW";
|
||||
r = @"BINARY";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
d = @"ALLOW";
|
||||
r = @"CERT";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateAllowScope:
|
||||
d = @"ALLOW";
|
||||
r = @"SCOPE";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateAllowUnknown:
|
||||
d = @"ALLOW";
|
||||
r = @"UNKNOWN";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateBlockBinary:
|
||||
d = @"DENY";
|
||||
r = @"BINARY";
|
||||
break;
|
||||
case SNTEventStateBlockCertificate:
|
||||
d = @"DENY";
|
||||
r = @"CERT";
|
||||
break;
|
||||
case SNTEventStateBlockScope:
|
||||
d = @"DENY";
|
||||
r = @"SCOPE";
|
||||
break;
|
||||
case SNTEventStateBlockUnknown:
|
||||
d = @"DENY";
|
||||
r = @"UNKNOWN";
|
||||
break;
|
||||
default:
|
||||
d = @"ALLOW";
|
||||
r = @"NOTRUNNING";
|
||||
logArgs = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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 appendFormat:@"|explain=%@", cd.decisionExtra];
|
||||
}
|
||||
|
||||
[outLog appendFormat:@"|sha256=%@", cd.sha256];
|
||||
|
||||
if (cd.certSHA256) {
|
||||
[outLog appendFormat:@"|cert_sha256=%@|cert_cn=%@", cd.certSHA256,
|
||||
[self sanitizeString:cd.certCommonName]];
|
||||
}
|
||||
|
||||
if (cd.quarantineURL) {
|
||||
[outLog appendFormat:@"|quarantine_url=%@", [self sanitizeString:cd.quarantineURL]];
|
||||
}
|
||||
|
||||
NSString *mode;
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor:
|
||||
mode = @"M"; break;
|
||||
case SNTClientModeLockdown:
|
||||
mode = @"L"; break;
|
||||
default:
|
||||
mode = @"U"; break;
|
||||
}
|
||||
|
||||
[outLog appendFormat:@"|pid=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@|mode=%@|path=%@",
|
||||
message.pid, message.ppid,
|
||||
message.uid, [self nameForUID:message.uid],
|
||||
message.gid, [self nameForGID:message.gid],
|
||||
mode, [self sanitizeString:@(message.path)]];
|
||||
|
||||
// Check for app translocation by GateKeeper, and log original path if the case.
|
||||
NSString *originalPath = [self originalPathForTranslocation:message];
|
||||
if (originalPath) {
|
||||
[outLog appendFormat:@"|origpath=%@", [self sanitizeString:originalPath]];
|
||||
}
|
||||
|
||||
if (logArgs) {
|
||||
[self addArgsForPid:message.pid toString:outLog];
|
||||
}
|
||||
|
||||
LOGI(@"%@", outLog);
|
||||
}
|
||||
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties {
|
||||
NSString *dmgPath = @"";
|
||||
NSString *serial = @"";
|
||||
if ([diskProperties[@"DADeviceModel"] isEqual:@"Disk Image"]) {
|
||||
dmgPath = [self diskImageForDevice:diskProperties[@"DADevicePath"]];
|
||||
} else {
|
||||
serial = [self serialForDevice:diskProperties[@"DADevicePath"]];
|
||||
serial = [serial stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
}
|
||||
|
||||
NSString *model = [NSString stringWithFormat:@"%@ %@",
|
||||
diskProperties[@"DADeviceVendor"] ?: @"",
|
||||
diskProperties[@"DADeviceModel"] ?: @""];
|
||||
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
double appearance = [diskProperties[@"DAAppearanceTime"] doubleValue];
|
||||
NSString *appearanceDateString =
|
||||
[_dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:appearance]];
|
||||
|
||||
NSString *log =
|
||||
@"action=DISKAPPEAR|mount=%@|volume=%@|bsdname=%@|fs=%@|"
|
||||
@"model=%@|serial=%@|bus=%@|dmgpath=%@|appearance=%@";
|
||||
LOGI(log,
|
||||
[diskProperties[@"DAVolumePath"] path] ?: @"",
|
||||
diskProperties[@"DAVolumeName"] ?: @"",
|
||||
diskProperties[@"DAMediaBSDName"] ?: @"",
|
||||
diskProperties[@"DAVolumeKind"] ?: @"",
|
||||
model ?: @"",
|
||||
serial,
|
||||
diskProperties[@"DADeviceProtocol"] ?: @"",
|
||||
dmgPath,
|
||||
appearanceDateString);
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {
|
||||
LOGI(@"action=DISKDISAPPEAR|mount=%@|volume=%@|bsdname=%@",
|
||||
[diskProperties[@"DAVolumePath"] path] ?: @"",
|
||||
diskProperties[@"DAVolumeName"] ?: @"",
|
||||
diskProperties[@"DAMediaBSDName"]);
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
- (void)logFileModification:(santa_message_t)message {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)logAllowedExecution:(santa_message_t)message {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)writeLog:(NSString *)log {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)cacheDecision:(SNTCachedDecision *)cd {
|
||||
dispatch_sync(self.detailStoreQueue, ^{
|
||||
self.detailStore[@(cd.vnodeId)] = cd;
|
||||
});
|
||||
}
|
||||
|
||||
- (SNTCachedDecision *)cachedDecisionForMessage:(santa_message_t)message {
|
||||
__block SNTCachedDecision *cd;
|
||||
dispatch_sync(self.detailStoreQueue, ^{
|
||||
cd = self.detailStore[@(message.vnode_id)];
|
||||
});
|
||||
return cd;
|
||||
}
|
||||
|
||||
/**
|
||||
Sanitizes a given string if necessary, otherwise returns the original.
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2018 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,24 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@import Foundation;
|
||||
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
@class SNTCachedDecision;
|
||||
|
||||
///
|
||||
/// Logs execution and file write events to syslog
|
||||
///
|
||||
@interface SNTEventLog : NSObject
|
||||
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties;
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties;
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message;
|
||||
|
||||
- (void)saveDecisionDetails:(SNTCachedDecision *)cd;
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message;
|
||||
- (void)logAllowedExecution:(santa_message_t)message;
|
||||
#import "SNTSyslogEventLog.h"
|
||||
|
||||
@interface SNTFileEventLog : SNTSyslogEventLog
|
||||
@end
|
||||
101
Source/santad/Logs/SNTFileEventLog.m
Normal file
101
Source/santad/Logs/SNTFileEventLog.m
Normal file
@@ -0,0 +1,101 @@
|
||||
/// Copyright 2018 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 "SNTFileEventLog.h"
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStrengthify.h"
|
||||
|
||||
@interface SNTFileEventLog ()
|
||||
@property NSFileHandle *fh;
|
||||
@property(readonly, nonatomic) dispatch_queue_t q;
|
||||
@property dispatch_source_t source;
|
||||
@property(readonly, nonatomic) dispatch_source_t timer;
|
||||
@property(readonly, nonatomic) NSString *path;
|
||||
@property(readonly, nonatomic) NSMutableData *buffer;
|
||||
@end
|
||||
|
||||
@implementation SNTFileEventLog
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_q = dispatch_queue_create("com.google.santa.file_event_log", DISPATCH_QUEUE_SERIAL);
|
||||
_path = [[SNTConfigurator configurator] eventLogPath];
|
||||
_fh = [self fileHandleForPath:_path];
|
||||
[self watchLogFile];
|
||||
// 8k buffer to batch logs for writing.
|
||||
_buffer = [NSMutableData dataWithCapacity:8192];
|
||||
// To avoid long lulls in the log being updated, flush the buffer every second.
|
||||
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _q);
|
||||
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), NSEC_PER_SEC * 1, 0);
|
||||
WEAKIFY(self);
|
||||
dispatch_source_set_event_handler(_timer, ^{
|
||||
STRONGIFY(self);
|
||||
[self flushBuffer];
|
||||
});
|
||||
dispatch_resume(_timer);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSFileHandle *)fileHandleForPath:(NSString *)path {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:path]) {
|
||||
[fm createFileAtPath:path contents:nil attributes:nil];
|
||||
}
|
||||
NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:path];
|
||||
[fh seekToEndOfFile];
|
||||
return fh;
|
||||
}
|
||||
|
||||
- (void)watchLogFile {
|
||||
if (self.source) {
|
||||
dispatch_source_set_event_handler_f(self.source, NULL);
|
||||
dispatch_source_cancel(self.source);
|
||||
}
|
||||
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
|
||||
self.fh.fileDescriptor,
|
||||
DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME,
|
||||
self.q);
|
||||
WEAKIFY(self);
|
||||
dispatch_source_set_event_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
[self.fh closeFile];
|
||||
self.fh = [self fileHandleForPath:self.path];
|
||||
[self watchLogFile];
|
||||
});
|
||||
dispatch_resume(self.source);
|
||||
}
|
||||
|
||||
- (void)writeLog:(NSString *)log {
|
||||
dispatch_async(self.q, ^{
|
||||
NSString *dateString = [self.dateFormatter stringFromDate:[NSDate date]];
|
||||
NSString *outLog = [NSString stringWithFormat:@"[%@] I santad: %@\n", dateString, log];
|
||||
[self.buffer appendBytes:outLog.UTF8String
|
||||
length:[outLog lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
|
||||
// Avoid excessive calls to write() by batching logs.
|
||||
if (self.buffer.length >= 4096) {
|
||||
[self flushBuffer];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)flushBuffer {
|
||||
write(self.fh.fileDescriptor, self.buffer.bytes, self.buffer.length);
|
||||
[self.buffer setLength:0];
|
||||
}
|
||||
|
||||
@end
|
||||
18
Source/santad/Logs/SNTSyslogEventLog.h
Normal file
18
Source/santad/Logs/SNTSyslogEventLog.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/// Copyright 2018 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 "SNTEventLog.h"
|
||||
|
||||
@interface SNTSyslogEventLog : SNTEventLog
|
||||
@end
|
||||
242
Source/santad/Logs/SNTSyslogEventLog.m
Normal file
242
Source/santad/Logs/SNTSyslogEventLog.m
Normal file
@@ -0,0 +1,242 @@
|
||||
/// Copyright 2018 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 "SNTSyslogEventLog.h"
|
||||
|
||||
#import <libproc.h>
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTSyslogEventLog
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message {
|
||||
NSString *action, *newpath;
|
||||
|
||||
NSString *path = @(message.path);
|
||||
|
||||
switch (message.action) {
|
||||
case ACTION_NOTIFY_DELETE: {
|
||||
action = @"DELETE";
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXCHANGE: {
|
||||
action = @"EXCHANGE";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_LINK: {
|
||||
action = @"LINK";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_RENAME: {
|
||||
action = @"RENAME";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
action = @"WRITE";
|
||||
break;
|
||||
}
|
||||
default: action = @"UNKNOWN"; break;
|
||||
}
|
||||
|
||||
// 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 appendFormat:@"|newpath=%@", [self sanitizeString:newpath]];
|
||||
}
|
||||
char ppath[PATH_MAX] = "(null)";
|
||||
proc_pidpath(message.pid, ppath, PATH_MAX);
|
||||
|
||||
[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]];
|
||||
|
||||
[self writeLog:outStr];
|
||||
}
|
||||
|
||||
- (void)logExecution:(santa_message_t)message withDecision:(SNTCachedDecision *)cd {
|
||||
NSString *d, *r;
|
||||
BOOL logArgs = NO;
|
||||
|
||||
switch (cd.decision) {
|
||||
case SNTEventStateAllowBinary:
|
||||
d = @"ALLOW";
|
||||
r = @"BINARY";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
d = @"ALLOW";
|
||||
r = @"CERT";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateAllowScope:
|
||||
d = @"ALLOW";
|
||||
r = @"SCOPE";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateAllowUnknown:
|
||||
d = @"ALLOW";
|
||||
r = @"UNKNOWN";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateBlockBinary:
|
||||
d = @"DENY";
|
||||
r = @"BINARY";
|
||||
break;
|
||||
case SNTEventStateBlockCertificate:
|
||||
d = @"DENY";
|
||||
r = @"CERT";
|
||||
break;
|
||||
case SNTEventStateBlockScope:
|
||||
d = @"DENY";
|
||||
r = @"SCOPE";
|
||||
break;
|
||||
case SNTEventStateBlockUnknown:
|
||||
d = @"DENY";
|
||||
r = @"UNKNOWN";
|
||||
break;
|
||||
default:
|
||||
d = @"ALLOW";
|
||||
r = @"NOTRUNNING";
|
||||
logArgs = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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 appendFormat:@"|explain=%@", cd.decisionExtra];
|
||||
}
|
||||
|
||||
[outLog appendFormat:@"|sha256=%@", cd.sha256];
|
||||
|
||||
if (cd.certSHA256) {
|
||||
[outLog appendFormat:@"|cert_sha256=%@|cert_cn=%@", cd.certSHA256,
|
||||
[self sanitizeString:cd.certCommonName]];
|
||||
}
|
||||
|
||||
if (cd.quarantineURL) {
|
||||
[outLog appendFormat:@"|quarantine_url=%@", [self sanitizeString:cd.quarantineURL]];
|
||||
}
|
||||
|
||||
NSString *mode;
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor:
|
||||
mode = @"M"; break;
|
||||
case SNTClientModeLockdown:
|
||||
mode = @"L"; break;
|
||||
default:
|
||||
mode = @"U"; break;
|
||||
}
|
||||
|
||||
[outLog appendFormat:@"|pid=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@|mode=%@|path=%@",
|
||||
message.pid, message.ppid,
|
||||
message.uid, [self nameForUID:message.uid],
|
||||
message.gid, [self nameForGID:message.gid],
|
||||
mode, [self sanitizeString:@(message.path)]];
|
||||
|
||||
// Check for app translocation by GateKeeper, and log original path if the case.
|
||||
NSString *originalPath = [self originalPathForTranslocation:message];
|
||||
if (originalPath) {
|
||||
[outLog appendFormat:@"|origpath=%@", [self sanitizeString:originalPath]];
|
||||
}
|
||||
|
||||
if (logArgs) {
|
||||
[self addArgsForPid:message.pid toString:outLog];
|
||||
}
|
||||
|
||||
[self writeLog:outLog];
|
||||
}
|
||||
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message {
|
||||
[self logExecution:message withDecision:cd];
|
||||
}
|
||||
|
||||
- (void)logAllowedExecution:(santa_message_t)message {
|
||||
SNTCachedDecision *cd = [self cachedDecisionForMessage:message];
|
||||
[self logExecution:message withDecision:cd];
|
||||
}
|
||||
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties {
|
||||
NSString *dmgPath = @"";
|
||||
NSString *serial = @"";
|
||||
if ([diskProperties[@"DADeviceModel"] isEqual:@"Disk Image"]) {
|
||||
dmgPath = [self diskImageForDevice:diskProperties[@"DADevicePath"]];
|
||||
} else {
|
||||
serial = [self serialForDevice:diskProperties[@"DADevicePath"]];
|
||||
serial = [serial stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
}
|
||||
|
||||
NSString *model = [NSString stringWithFormat:@"%@ %@",
|
||||
diskProperties[@"DADeviceVendor"] ?: @"",
|
||||
diskProperties[@"DADeviceModel"] ?: @""];
|
||||
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
double a = [diskProperties[@"DAAppearanceTime"] doubleValue];
|
||||
NSString *appearanceDateString =
|
||||
[self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:a]];
|
||||
|
||||
NSString *format =
|
||||
@"action=DISKAPPEAR|mount=%@|volume=%@|bsdname=%@|fs=%@|"
|
||||
@"model=%@|serial=%@|bus=%@|dmgpath=%@|appearance=%@";
|
||||
NSString *outLog = [NSMutableString stringWithFormat:format,
|
||||
[diskProperties[@"DAVolumePath"] path] ?: @"",
|
||||
diskProperties[@"DAVolumeName"] ?: @"",
|
||||
diskProperties[@"DAMediaBSDName"] ?: @"",
|
||||
diskProperties[@"DAVolumeKind"] ?: @"",
|
||||
model ?: @"",
|
||||
serial,
|
||||
diskProperties[@"DADeviceProtocol"] ?: @"",
|
||||
dmgPath,
|
||||
appearanceDateString];
|
||||
[self writeLog:outLog];
|
||||
}
|
||||
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {
|
||||
NSString *format = @"action=DISKDISAPPEAR|mount=%@|volume=%@|bsdname=%@";
|
||||
NSString *outLog = [NSMutableString stringWithFormat:format,
|
||||
[diskProperties[@"DAVolumePath"] path] ?: @"",
|
||||
diskProperties[@"DAVolumeName"] ?: @"",
|
||||
diskProperties[@"DAMediaBSDName"]];
|
||||
[self writeLog:outLog];
|
||||
}
|
||||
|
||||
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events {
|
||||
for (SNTStoredEvent *event in events) {
|
||||
NSString *format = @"action=DISKDISAPPEAR|mount=%@|volume=%@|bsdname=%@";
|
||||
NSString *outLog = [NSMutableString stringWithFormat:format,
|
||||
event.fileSHA256,
|
||||
event.fileBundleHash,
|
||||
event.fileBundleName,
|
||||
event.fileBundleID,
|
||||
event.fileBundlePath,
|
||||
event.filePath];
|
||||
[self writeLog:outLog];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)writeLog:(NSString *)log {
|
||||
LOGI(@"%@", log);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -16,33 +16,32 @@
|
||||
|
||||
@import DiskArbitration;
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDaemonControlController.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTEventLog.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTExecutionController.h"
|
||||
#import "SNTFileWatcher.h"
|
||||
#import "SNTFileEventLog.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTNotificationQueue.h"
|
||||
#import "SNTRuleTable.h"
|
||||
#import "SNTSyncdQueue.h"
|
||||
#import "SNTSyslogEventLog.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 SNTXPCConnection *controlConnection;
|
||||
@property SNTNotificationQueue *notQueue;
|
||||
@property pid_t syncdPID;
|
||||
@end
|
||||
|
||||
@implementation SNTApplication
|
||||
@@ -70,19 +69,50 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
SNTNotificationQueue *notQueue = [[SNTNotificationQueue alloc] init];
|
||||
// Choose an event logger.
|
||||
SNTConfigurator *configurator = [SNTConfigurator configurator];
|
||||
switch ([configurator eventLogType]) {
|
||||
case SNTEventLogTypeSyslog:
|
||||
_eventLog = [[SNTSyslogEventLog alloc] init];
|
||||
break;
|
||||
case SNTEventLogTypeFilelog:
|
||||
_eventLog = [[SNTFileEventLog alloc] init];
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
[configurator addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(clientMode))
|
||||
options:bits
|
||||
context:NULL];
|
||||
[configurator addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(syncBaseURL))
|
||||
options:bits
|
||||
context:NULL];
|
||||
[configurator addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(whitelistPathRegex))
|
||||
options:bits
|
||||
context:NULL];
|
||||
[configurator addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(blacklistPathRegex))
|
||||
options:bits
|
||||
context:NULL];
|
||||
|
||||
// Establish XPC listener for Santa and santactl connections
|
||||
SNTDaemonControlController *dc = [[SNTDaemonControlController alloc] init];
|
||||
dc.driverManager = _driverManager;
|
||||
dc.notQueue = notQueue;
|
||||
dc.syncdQueue = syncdQueue;
|
||||
SNTDaemonControlController *dc =
|
||||
[[SNTDaemonControlController alloc] initWithDriverManager:_driverManager
|
||||
notificationQueue:self.notQueue
|
||||
syncdQueue:syncdQueue
|
||||
eventLog:_eventLog];
|
||||
|
||||
_controlConnection =
|
||||
[[SNTXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]];
|
||||
@@ -90,52 +120,11 @@
|
||||
_controlConnection.exportedObject = dc;
|
||||
[_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) {
|
||||
const char *cPath = [kDefaultConfigFilePath fileSystemRepresentation];
|
||||
struct stat fileStat;
|
||||
stat(cPath, &fileStat);
|
||||
int mask = S_IRWXU | S_IRWXG | S_IRWXO;
|
||||
int desired = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
|
||||
if (fileStat.st_uid != 0 || fileStat.st_gid != 0 || (fileStat.st_mode & mask) != desired) {
|
||||
LOGD(@"Config file permissions changed, fixing.");
|
||||
chown(cPath, 0, 0);
|
||||
chmod(cPath, desired);
|
||||
}
|
||||
} else {
|
||||
LOGD(@"Config file changed, reloading.");
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
_eventLog = [[SNTEventLog alloc] init];
|
||||
|
||||
// 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.
|
||||
@@ -147,18 +136,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.");
|
||||
|
||||
@@ -275,4 +252,84 @@ 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 {
|
||||
NSString *newKey = NSKeyValueChangeNewKey;
|
||||
NSString *oldKey = NSKeyValueChangeOldKey;
|
||||
if ([keyPath isEqualToString:NSStringFromSelector(@selector(clientMode))]) {
|
||||
SNTClientMode new =
|
||||
[change[newKey] isKindOfClass:[NSNumber class]] ? [change[newKey] longLongValue] : 0;
|
||||
SNTClientMode old =
|
||||
[change[oldKey] isKindOfClass:[NSNumber class]] ? [change[oldKey] longLongValue] : 0;
|
||||
if (new != old) [self clientModeDidChange:new];
|
||||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(syncBaseURL))]) {
|
||||
NSURL *new = [change[newKey] isKindOfClass:[NSURL class]] ? change[newKey] : nil;
|
||||
NSURL *old = [change[oldKey] isKindOfClass:[NSURL class]] ? change[oldKey] : nil;
|
||||
if (!new && !old) return;
|
||||
if (![new.absoluteString isEqualToString:old.absoluteString]) [self syncBaseURLDidChange:new];
|
||||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(whitelistPathRegex))] ||
|
||||
[keyPath isEqualToString:NSStringFromSelector(@selector(blacklistPathRegex))]) {
|
||||
NSRegularExpression *new =
|
||||
[change[newKey] isKindOfClass:[NSRegularExpression class]] ? change[newKey] : nil;
|
||||
NSRegularExpression *old =
|
||||
[change[oldKey] isKindOfClass:[NSRegularExpression class]] ? change[oldKey] : 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);
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:[SNTConfigurator configurator]
|
||||
selector:@selector(clearSyncState)
|
||||
object:nil];
|
||||
[self startSyncd];
|
||||
} else {
|
||||
LOGI(@"SyncBaseURL removed, killing santactl pid: %i", self.syncdPID);
|
||||
[self stopSyncd];
|
||||
// Keep the syncState active for 10 min in case com.apple.ManagedClient is flapping.
|
||||
[[SNTConfigurator configurator] performSelector:@selector(clearSyncState)
|
||||
withObject:nil
|
||||
afterDelay:600];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@class SNTDriverManager;
|
||||
@class SNTEventLog;
|
||||
@class SNTNotificationQueue;
|
||||
@class SNTSyncdQueue;
|
||||
|
||||
@@ -25,8 +26,8 @@
|
||||
///
|
||||
@interface SNTDaemonControlController : NSObject<SNTDaemonControlXPC>
|
||||
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTNotificationQueue *notQueue;
|
||||
@property SNTSyncdQueue *syncdQueue;
|
||||
|
||||
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
|
||||
notificationQueue:(SNTNotificationQueue *)notQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue
|
||||
eventLog:(SNTEventLog *)eventLog;
|
||||
@end
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTEventLog.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTNotificationQueue.h"
|
||||
@@ -42,15 +43,26 @@ double watchdogRAMPeak = 0;
|
||||
@interface SNTDaemonControlController ()
|
||||
@property NSString *_syncXsrfToken;
|
||||
@property SNTPolicyProcessor *policyProcessor;
|
||||
@property SNTEventLog *eventLog;
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTNotificationQueue *notQueue;
|
||||
@property SNTSyncdQueue *syncdQueue;
|
||||
@end
|
||||
|
||||
@implementation SNTDaemonControlController
|
||||
|
||||
- (instancetype)init {
|
||||
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
|
||||
notificationQueue:(SNTNotificationQueue *)notQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue
|
||||
eventLog:(SNTEventLog *)eventLog {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:
|
||||
[SNTDatabaseController ruleTable]];
|
||||
_driverManager = driverManager;
|
||||
_notQueue = notQueue;
|
||||
_syncdQueue = syncdQueue;
|
||||
_eventLog = eventLog;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -134,10 +146,7 @@ double watchdogRAMPeak = 0;
|
||||
}
|
||||
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply {
|
||||
if ([[SNTConfigurator configurator] clientMode] != mode) {
|
||||
[[SNTConfigurator configurator] setClientMode:mode];
|
||||
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:mode];
|
||||
}
|
||||
[[SNTConfigurator configurator] setSyncServerClientMode:mode];
|
||||
reply();
|
||||
}
|
||||
|
||||
@@ -150,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();
|
||||
@@ -169,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();
|
||||
}
|
||||
|
||||
@@ -179,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();
|
||||
}
|
||||
|
||||
@@ -208,6 +225,7 @@ double watchdogRAMPeak = 0;
|
||||
bs.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
|
||||
[bs resume];
|
||||
[[bs remoteObjectProxy] setBundleNotificationListener:listener];
|
||||
[bs invalidate];
|
||||
}
|
||||
|
||||
#pragma mark syncd Ops
|
||||
@@ -281,6 +299,9 @@ double watchdogRAMPeak = 0;
|
||||
// Add the updated event.
|
||||
[eventTable addStoredEvent:event];
|
||||
|
||||
// Log all of the generated bundle events.
|
||||
[self.eventLog logBundleHashingEvents:events];
|
||||
|
||||
WEAKIFY(self);
|
||||
|
||||
// Sync the updated event. If the sync server needs the related events, add them to the eventTable
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "SNTBlockMessage.h"
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
@@ -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,11 +108,20 @@
|
||||
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
|
||||
error:&csError];
|
||||
// Ignore codesigning if there are any errors with the signature.
|
||||
MOLCodesignChecker *csInfo =
|
||||
[[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path
|
||||
fileDescriptor:binInfo.fileHandle.fileDescriptor
|
||||
error:&csError];
|
||||
// Ignore codesigning if there are any errors with the signature.
|
||||
if (csError) csInfo = nil;
|
||||
|
||||
// Actually make the decision.
|
||||
@@ -121,7 +136,7 @@
|
||||
(SNTEventStateAllow & cd.decision) ? ACTION_RESPOND_ALLOW : ACTION_RESPOND_DENY;
|
||||
|
||||
// Save decision details for logging the execution later.
|
||||
if (action == ACTION_RESPOND_ALLOW) [_eventLog saveDecisionDetails:cd];
|
||||
if (action == ACTION_RESPOND_ALLOW) [_eventLog cacheDecision:cd];
|
||||
|
||||
// Send the decision to the kernel.
|
||||
[_driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
#import <MOLCertificate.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
@class SNTCachedDecision;
|
||||
@class SNTFileInfo;
|
||||
|
||||
@@ -132,6 +132,8 @@
|
||||
/// @return @c YES if file is in scope, @c NO otherwise.
|
||||
///
|
||||
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
|
||||
if (!fi) return nil;
|
||||
|
||||
// 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)]) {
|
||||
@@ -147,6 +149,8 @@
|
||||
}
|
||||
|
||||
- (NSString *)fileIsScopeBlacklisted:(SNTFileInfo *)fi {
|
||||
if (!fi) return nil;
|
||||
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] blacklistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Blacklist Regex";
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -47,7 +47,10 @@
|
||||
|
||||
self.mockCodesignChecker = OCMClassMock([MOLCodesignChecker class]);
|
||||
OCMStub([self.mockCodesignChecker alloc]).andReturn(self.mockCodesignChecker);
|
||||
OCMStub([self.mockCodesignChecker initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:NULL]])
|
||||
|
||||
OCMStub([self.mockCodesignChecker initWithBinaryPath:OCMOCK_ANY
|
||||
fileDescriptor:0
|
||||
error:[OCMArg setTo:NULL]])
|
||||
.andReturn(self.mockCodesignChecker);
|
||||
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@import XCTest;
|
||||
|
||||
#import "SNTFileWatcher.h"
|
||||
|
||||
@interface SNTFileWatcherTest : XCTestCase
|
||||
@property NSFileManager *fm;
|
||||
@property NSString *file;
|
||||
@end
|
||||
|
||||
@implementation SNTFileWatcherTest
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
|
||||
self.fm = [NSFileManager defaultManager];
|
||||
self.file = [NSTemporaryDirectory() stringByAppendingString:@"SNTFileWatcherTest_File"];
|
||||
[self createFile];
|
||||
usleep(10000);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
[self deleteFile];
|
||||
usleep(10000);
|
||||
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)createFile {
|
||||
[self.fm createFileAtPath:self.file contents:nil attributes:nil];
|
||||
}
|
||||
|
||||
- (void)deleteFile {
|
||||
[self.fm removeItemAtPath:self.file error:NULL];
|
||||
}
|
||||
|
||||
- (void)testPlainInit {
|
||||
XCTAssertThrows([[SNTFileWatcher alloc] init]);
|
||||
}
|
||||
|
||||
- (void)testInitFileExists {
|
||||
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Init: callback called"];
|
||||
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
|
||||
handler:^(unsigned long data) {
|
||||
[exp fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testInitNewFile {
|
||||
[self deleteFile];
|
||||
|
||||
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Init: callback called"];
|
||||
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
|
||||
handler:^(unsigned long data) {
|
||||
[exp fulfill];
|
||||
}];
|
||||
|
||||
[self createFile];
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testFileChanged {
|
||||
__block BOOL fulfilled = NO;
|
||||
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Changed: callback called"];
|
||||
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
|
||||
handler:^(unsigned long data) {
|
||||
NSString *d = [NSString stringWithContentsOfFile:self.file
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
if (!fulfilled && [d isEqual:@"0x8BADF00D"]) {
|
||||
fulfilled = YES;
|
||||
[exp fulfill];
|
||||
}
|
||||
}];
|
||||
|
||||
sleep(1);
|
||||
|
||||
[[@"0x8BADF00D" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:self.file atomically:NO];
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testFileReplaced {
|
||||
__block BOOL fulfilled = NO;
|
||||
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Replaced: callback called"];
|
||||
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
|
||||
handler:^(unsigned long data) {
|
||||
NSString *d = [NSString stringWithContentsOfFile:self.file
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
if (!fulfilled && [d isEqual:@"0xFACEFEED"]) {
|
||||
fulfilled = YES;
|
||||
[exp fulfill];
|
||||
}
|
||||
}];
|
||||
|
||||
[[@"0xFACEFEED" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:self.file atomically:YES];
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testFileExtended {
|
||||
int fd = open(self.file.fileSystemRepresentation, O_WRONLY);
|
||||
write(fd, "0xDEAD", 6);
|
||||
|
||||
__block BOOL fulfilled = NO;
|
||||
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Extended: callback called"];
|
||||
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
|
||||
handler:^(unsigned long data) {
|
||||
int file = open(self.file.fileSystemRepresentation, O_RDONLY);
|
||||
char fileData[10];
|
||||
read(file, fileData, 10);
|
||||
|
||||
if (!fulfilled && strncmp(fileData, "0xDEADBEEF", 10) == 0) {
|
||||
fulfilled = YES;
|
||||
[exp fulfill];
|
||||
}
|
||||
}];
|
||||
|
||||
write(fd, "BEEF", 4);
|
||||
close(fd);
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user