Compare commits

...

156 Commits

Author SHA1 Message Date
Russell Hancox
a67801d5ed santactl/status: Remove driver connected, re-org USB blocking status (#826) 2022-06-22 14:59:46 -04:00
Russell Hancox
3d37a3a5ae santad: Update assert usage to avoid a string-to-bool conversion (#825) 2022-06-22 12:55:57 -04:00
Russell Hancox
bfae5dc828 fix some style issues (#824) 2022-06-22 10:41:05 -04:00
Pete Markowsky
fde5f52a11 Added handling for Remount events to USB mass storage blocking (#818)
* Added handling for Remount events to USB mass storage blocking.
2022-06-22 09:39:20 -04:00
Russell Hancox
01bd1bfdca santad: Use multiple semaphores to avoid freeing ES message before use of it has ended. (#822)
This slightly complex solution is necessary because while on macOS 11+ there are retain/release methods that can be used on ES messages, on 10.15 the only option is a copy which is comparatively expensive (and on 11+ the copy/free methods are aliases for retain/release)

Fixes #821
2022-06-08 11:21:40 -04:00
Matt W
ae13900676 Mute self to reduce message volume. Remove noisy log message. (#820)
* Mute self to reduce message volume. Remove noisy log message.

* Bail if self muting failed. Remove selfPid.

* Fix tests by mocking es_mute_process
2022-05-31 11:36:35 -04:00
Matt W
a65c91874b Copy new PrinterProxy file instead of overwriting (#819)
* Copy new PrinterProxy file instead of overwriting

* Update log type for error message

* Update log message severity
2022-05-27 13:08:25 -04:00
Matt W
6a3fda069c Remove unused testing scripts (#816)
* Remvoe unused testing scripts

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2022-05-27 11:03:10 -04:00
Khalid Jamal Abdulnasser
4d34099142 santad: log decision when failing to read file (#817) 2022-05-27 09:52:06 -04:00
Russell Hancox
e639574973 Project: Fix layering for tests (#813) 2022-05-12 16:52:11 -04:00
Russell Hancox
636f9ea873 Project: Layering, missed a dependency (#812) 2022-05-12 14:49:18 -04:00
Russell Hancox
9099409915 Project: Enable layering check, fix all dependency violations (#811) 2022-05-12 14:26:08 -04:00
Russell Hancox
976f483a99 syncservice: Fix SNTSyncTest (#810)
Failing preflight early if the daemonConn doesn't return a response the tests. This fix is a bit awkward, I tried to add the defaults in setUp but then you can't overwrite the stubs in methods that need to do it
2022-05-12 09:54:00 -04:00
Tom Burgin
8a32b7a56b preflight sync: fix dispatch_group_wait return polarity (#809)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-11 14:55:42 -04:00
Tom Burgin
7eeb06b406 preflight sync: stop the sync if we cannot communicate with the daemon (#808)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-11 18:45:58 +00:00
Tom Burgin
4540a1c656 SNTConfigurator: remove mutability from sync state dict (#807)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-11 14:35:43 -04:00
Russell Hancox
acc7b32b24 GUI: Switch to UserNotification.framework notifications (#806) 2022-05-11 12:32:08 -04:00
Russell Hancox
b92d513f5d GUI: Fix message queuing (#805) 2022-05-11 09:59:38 -04:00
Tom Burgin
3458fccd4e santasyncservice: handle loading and unloading of the service in the pkg (#804)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-10 14:59:34 -04:00
Russell Hancox
fdfb00368c GUI: Update keys for EventDetailURL. (#802)
The previous change here (#797) was not backward compatible and would be difficult to roll out. This change restores the previously used key and adds 2 new ones for migration. The previous key is marked deprecated and will be removed in the future.
2022-05-09 13:46:13 -04:00
Tom Burgin
6bd369cfb2 santad: remove sema from sync service queue (#803)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-09 13:32:28 -04:00
Pete Markowsky
0df26c6214 Fix ES Mock Client Subscription issues (#801)
Fixes an issue with the ES mock where it was deleting all clients on an unsubscribe.
2022-05-06 14:34:42 -04:00
Russell Hancox
6e22da1d97 santad: Add 'null' event logger. Fixes #754 (#799) 2022-05-06 12:22:04 -04:00
Russell Hancox
1725809335 Add config to allow uploading all events (#800)
* Add config to allow uploading all events

This config can be enabled locally or by a sync server and causes the
client to upload all events, not just those for binaries that are or
would be blocked.

Fixes #689
2022-05-06 11:45:53 -04:00
Pete Markowsky
3eff49feda Added macos-12 to the build matrix. (#798) 2022-05-03 21:14:15 -04:00
Pete Markowsky
5caedebb06 Created a profiles package so provisioning profiles only need to be in one place. (#794) 2022-05-03 17:14:02 -04:00
Russell Hancox
d823028b72 Sync: Add option to enable event upload despite clean sync. (#796)
Related to #789
2022-05-03 15:15:42 -04:00
Russell Hancox
49b2d6e22a GUI: Add %bundle_or_file_sha% translation key (#797)
* GUI: Add %bundle_or_file_sha% translation key

This mimics the current behavior that %file_sha% previously had and
moves %file_sha% to the expected behavior or just showing the file's
SHA.

Related to #795
2022-05-03 14:59:01 -04:00
Russell Hancox
4236d57e96 Project: Update packaging script to do tarball creation in a scratch dir (#793) 2022-04-28 15:25:48 -04:00
Russell Hancox
36d463a1dc Project: Include syncservice.plist in release builds and loads (#792) 2022-04-28 14:42:19 -04:00
Tom Burgin
adbafd6bab syncservice: sign and package (#790)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-04-28 13:31:20 -04:00
Tom Burgin
b5ebe1259c syncservice: implementation and migration (#775)
* review updates

* fix test

* review updates

* log level cleanup

Co-authored-by: Tom Burgin <bur@chromium.org>
Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2022-04-27 14:54:56 -04:00
Ryan Diers
e0ae0f481b santa/gui: Update buttons to push style to better stand out (#788) 2022-04-19 20:08:35 -04:00
Matt W
8037c79fc0 Populate critical paths from the ES default mute set (#786)
* Populate critical paths from the ES default mute set

* Attempt to fix build on older macos

* Link ES to build SNTRuleTableTest

* Workflow test

* Use preprocessor macros to support building on older SDKs

* Add API availability
2022-04-18 15:11:43 -04:00
Walter Lee
892d303de1 Disable layering check for Objective-C, part two (#787) 2022-04-18 12:15:08 -04:00
Russell Hancox
ff3979263e santad: Use TTY path provided by ES (#785) 2022-04-15 12:48:06 -04:00
np5
01afefd3d4 santactl/sync: Fix event team ID decision value (#784) 2022-04-15 10:27:48 -04:00
Kent Ma
830627e7bc Docs: Add "Team ID" to description on AllowedPathRegex (#782) 2022-04-14 13:13:51 -04:00
Walter Lee
601d726fcc Disable layering check for Objective-C (#781) 2022-04-12 09:06:55 -04:00
Tom Burgin
0be1ca0199 ES_EVENT_TYPE_NOTIFY_UNMOUNT: flush the cache off the ES handler thread (#778)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-04-06 12:07:08 -04:00
Kent Ma
8602593149 Fix dead link (#774) 2022-03-25 13:33:08 -04:00
Matt W
9bca601ce6 Modified build target names for santa proto (#772) 2022-03-25 13:07:57 -04:00
Kent Ma
c73acd59d4 Update logo image of Santa (#773) 2022-03-25 12:46:34 -04:00
Russell Hancox
3c334e8882 Project: Fix coverage collection (#770) 2022-03-24 11:33:46 -04:00
Russell Hancox
5f811cadf8 Project: Update apple_rules dep, add .bazelversion for bazelisk users (#769) 2022-03-23 17:34:04 -04:00
Russell Hancox
4252475de0 Project: Fix fallback version (#767) 2022-03-23 15:13:30 -04:00
Kent Ma
45f1822681 Exclude bazel-out from test coverage generation (#768) 2022-03-23 15:10:46 -04:00
Russell Hancox
498a23d907 Project: Make versioning dynamic through bazel's --embed-label. (#766)
The apple_rules allow versioning using an apple_bundle_version rule that extracts elements from an embedded label. We haven't been able to use this until now because the kernel extension needed access to the version in a define.
2022-03-23 14:53:51 -04:00
Russell Hancox
5dff8a18f4 santad: Split ES cache into root/non-root varieties (#765) 2022-03-23 09:43:14 -04:00
Russell Hancox
676c02626d santactl/metrics: Allow filtering metrics (#763) 2022-03-22 18:12:14 +00:00
Russell Hancox
64950d0a99 Project: Show test errors in output from CI (#764) 2022-03-22 11:39:01 -04:00
Kent Ma
16f74cb85c Remove references to santa-driver and the kernel extension from parts of the docs (#762) 2022-03-21 11:33:45 -04:00
Russell Hancox
aadc961429 santad: Clear caches when disks are unmounted. (#760)
This restores behavior that was recently removed
2022-03-18 13:38:35 -04:00
Russell Hancox
be66fd92f4 santactl/status: Re-org output in status re: USB Blocking. (#759) 2022-03-18 09:57:34 -04:00
Russell Hancox
feea349f25 Project: Remove kext signing/packaging (#755) 2022-03-16 17:08:59 -04:00
Kent Ma
1c04c3a257 Remove code guarded by #ifdef kernel macros (#752)
* Remove code guarded by #ifdef kernel macros
2022-03-15 14:38:40 -04:00
np5
818d3f645f santactl/sync: Add model identifier to preflight request (#751) 2022-03-15 14:24:05 +00:00
Pete Markowsky
15d6bb1f14 Made santad an early boot client to prevent racing other pids. (#750)
Make santad an early boot Endpoint Security Framework Client.
2022-03-15 10:16:40 -04:00
Kent Ma
211dbd123f Remove the Santa kernel extension. (#749)
This includes:

* All of the code in Source/santa_driver containing the kernel extension
* The SNTDriverManager event provider
* All workflows in our CI related to testing if the driver builds
* Installation of the driver in install.sh. Note that code uninstalling existing instances of the driver is still intentionally kept present.
* Kernel extension-specific build rules
* Renames SNTKernelCommon to SNTCommon
* Driver version output from santactl version
* The [SNTConfigurator EnableSystemExtension] configuration key
2022-03-14 18:17:02 -04:00
Matt W
c67364fe76 Protobuf support, maildir format logging (#731)
* Initial protobuf support, maildir logging

Fix build issues in the integration test

Deduped some test code

Formatting

Address feedback from draft PR

Removed legacy labels. Updated docs.

Add in metrics. Fix protobuf logging test.

* Now use the Any proto for the LogBatch wrapper

* Changes based on PR feedback

* Added gauge metrics for spool dir

* Formatting

* Add event time to proto

* Fix build issue after rebase

* Update BUILD rules

* Updated language around protobuf logging to mark as beta
2022-03-14 15:46:52 -04:00
Pete Markowsky
2043983f69 Fix typo in SNTDeviceManager tests & ensure they run in the CI. (#746) 2022-03-14 12:57:07 -04:00
Russell Hancox
2f408936a0 Project: Disable bazel layering_check feature for most rules (#742) 2022-03-10 10:07:15 -05:00
Russell Hancox
02c1d0f267 Project: Bump version to 2022.3 (#745) 2022-03-10 09:35:44 -05:00
Pete Markowsky
4728c346cc Fix uninstall.sh to remove the metric & bundle services. (#743) 2022-03-09 18:00:45 -05:00
Pete Markowsky
9588dd8a0e Fix: Issue with SNTMetricHTTPWriter Timeouts (#741)
Fix issue with santametricservice timing out due to incorrect timeout argument.
2022-03-08 14:12:57 -05:00
Russell Hancox
e3e48aed1b Packaging: Keep package versions simple (#737) 2022-03-02 10:36:39 -05:00
Russell Hancox
e60f9cf6c5 Project: Add build version (#736) 2022-02-28 14:18:18 -05:00
Kent Ma
c7e309ccb1 Add a USB device blocking popup. (#728)
* Add a USB device blocking popup.
* Refactor SNTNotificationManager and SNTMessageWindowController to make
  for generalized notification logic
* Add the configuration keys for custom block messages and resize window
2022-02-28 13:30:56 -05:00
Russell Hancox
ad8aafbd07 Project: Bump version to 2022.2 (#734) 2022-02-17 11:36:21 -05:00
Russell Hancox
9e671c3dee Project: Add arm64 to hostArchitectures for productbuild (#733)
This should avoid prompts to install Rosetta when installing the .pkg
2022-02-16 20:02:12 -05:00
Russell Hancox
d97abe36f2 santad: Fix fail open tests in SNTExecutionControllerTest (#730) 2022-02-14 20:13:08 -05:00
Pete Markowsky
faa8946056 Fix: remediate a crash in santametricservice (#729)
* Fix issue with task cancelation.

* Make export timeouts configurable.

This allows an export timeout to be set via configuration and eases testing.
2022-02-14 13:51:29 -05:00
Kent Ma
8b2b1f0bfc Report USB blocking status with santactl status (#727)
* Report USB blocking status with santactl status
2022-02-10 16:02:40 -05:00
Kent Ma
16678cd5a0 Update version of bazel rules_apple to fix broken 12.3 builds (#726) 2022-02-10 14:32:43 -05:00
Matt W
0bd6a199a3 Fix additional strlcpy issue, simplify call paths (#723)
* Fix additional strlcpy issue, simplify call paths

* Remove unused interface from header
2022-02-03 12:53:22 -05:00
Russell Hancox
58e2b7e1b8 santad: Add fail-closed mode (#722) 2022-01-28 18:29:18 -05:00
Tom Burgin
b824a8e3e0 santad: only store events if there is a sync server configured (#721)
* santad: only store events if there is a sync server configured

* SNTExecutionControllerTest stub sync server

Co-authored-by: Tom Burgin <bur@chromium.org>
2022-01-27 15:55:51 -05:00
Kent Ma
25bf2a93e4 Add DiskArbitrationTestUtil to shim out DiskArbitration for unit testing (#720) 2022-01-25 13:45:03 -05:00
Russell Hancox
f1ea1b369f santactl/fileinfo: Switch certIndex to an NSNumber (#719)
* santactl/fileinfo: Switch certIndex to an NSNumber
2022-01-25 12:50:04 -05:00
Tom Burgin
5503a88308 rule download: return early on daemon timeout (#718)
* rule download: return early on daemon timeout

* wording update

Co-authored-by: Tom Burgin <bur@chromium.org>
2022-01-21 17:19:44 +00:00
Kent Ma
8cf0f8217d Add clang_analyzer generation (#717)
Fix warnings for unused variables. The other warnings are more
nontrivial & require some light refactoring to fix, and will come in a followup PR.
2022-01-21 17:14:44 +00:00
Russell Hancox
22799ffc2a Conf: Delete and clean-up ASL conf, enable signaling on newsyslog.conf. (#716)
* Conf: Delete and clean-up ASL conf, enable signaling on newsyslog.conf.

The ASL config is a remnant from when Santa did all logging via ASL before Apple deprecated ASL and replaced it with ULS, which doesn't allow redirecting messages to a file. The old config wasn't causing any problems except that it was handling battling newsyslog for rotation and had different parameters.

The signal change in the newsyslog.conf causes newsyslog to fallback on sending a (harmless) SIGHUP to syslogd, which has no effect on Santa except it also triggers a 10s sleep inside newsyslog between renaming the old file and beginning the compression, which is plenty of time for santad to notice the rename and start writing new logs to the newer file.
2022-01-19 11:29:39 -05:00
Pete Markowsky
cb61d0cc99 Create test suites for each component (#702)
Create test suites for each component.
2022-01-18 17:00:44 -05:00
Pete Markowsky
fb7447ceba Fix off-by one error in strlcpy. (#715) 2022-01-18 15:31:30 -05:00
Russell Hancox
45e51e9c09 santactl/fileinfo: Clarify valid index for cert-index (#714) 2022-01-13 14:35:30 -05:00
Russell Hancox
b0f0cdd4e6 santactl/fileinfo: Update --cert-index usage (#713)
* santactl/fileinfo: Update --cert-index usage.

Fixes #710
2022-01-13 13:04:38 -05:00
Kent Ma
65090d3ef2 Support rule downloading of Team ID rules (#709)
* Support syncing Team ID rules and using 'identifier' instead of 'sha256' in sync rules
2022-01-13 10:55:14 -05:00
Russell Hancox
9c80f79d82 Sync: Allow configuring proxies (#708)
* Sync: Allow configuring proxies

Fixes #672
2022-01-13 15:04:11 +00:00
Kent Ma
93adaea81e Add clang annotation for fallthrough (#712) 2022-01-12 13:56:47 -05:00
Russell Hancox
a125b340a5 santad: Don't use proc_pidpath when using ES (#707) 2022-01-11 20:32:29 -05:00
Kent Ma
fbd0de3d48 Add test coverage for syncing USB mounting options (#711) 2022-01-11 17:13:37 -05:00
Russell Hancox
6f2ae62bce Project: Explicitly set calendar on ISO8601 dates (#706) 2022-01-06 09:33:04 -05:00
Christopher Sauer
da29b20473 Update hedron_compile_commands (#704) 2021-12-30 07:59:35 -05:00
Kent Ma
197109a8ee USB mass storage blocking and remounting (#685)
* USB mass storage blocking.

* Add the sync service and config key for enabling mass USB storage blocking
* Update docs with the sync service key
* Add ability to forcibly remount USBs with different flags
* update EndpointSecurityTestUtil and tests that use it to properly handle multiple ES clients
2021-12-16 13:38:48 -05:00
Kent Ma
91f3168c7a Update santactl rule text to have accurate text for team IDs (#701) 2021-12-14 11:42:53 -05:00
Russell Hancox
a00ec41518 Project: Bump version to 2022.1 (#700) 2021-12-13 13:28:16 -05:00
Russell Hancox
c32248aaf7 santad: Fix PrinterProxy workaround for Monterey+ (#698) 2021-12-13 15:24:58 +00:00
Pete Markowsky
afd97bdf3e Removed the check for export metrics in santad. (#697)
Remove the check for export metrics in santad

Metrics are always collected but only exported to a monitoring system when all of the necessary config options are set. Since they're always collected santactl metrics should always return metrics data.
2021-12-13 10:23:05 -05:00
Tom Burgin
73c4875b1f santasyncservice: move sync code to the santasyncservice dir (#696)
* sync: move sync code from santactl dir to santasyncservice dir

* clang-format

* fix tests

* s/SNTCommandSync/SNTSync

* s/SNTCommandSync/SNTSync on content
2021-12-08 18:11:56 -05:00
Pete Markowsky
916fc8c0e6 Add a simple event counter to SNTExecutionController (#694)
Add a simple event counter for events per response.
2021-12-08 17:35:37 -05:00
Kent Ma
e59e6105f3 Update the Santa version number to 2021.9 (#695) 2021-12-08 17:01:43 -05:00
Pete Markowsky
216ac811eb Fix issue with reregistering metrics. (#693) 2021-12-07 15:32:15 -05:00
Pete Markowsky
48f92f5913 Ignore VSCode directories (#692) 2021-12-07 14:23:13 -05:00
Russell Hancox
6bb08d0490 Project: Add bazel commands extractor for VSCode integration (#690) 2021-12-06 13:39:23 -05:00
Pete Markowsky
82b71c0f20 Add a metrics command to santactl (#687)
Add a metrics command to santactl.
2021-12-02 14:30:39 -05:00
Allister Banks
10ccee9e4c Docs: EnableSysxCache docs, etc (#684)
* Add more Conf references, EnableSysxCache key, etc
* Updated link (even though previous config profile explainer link redirects accordingly) to profile spec.
* Added brief explanation of TCC/PPPC and made reference to the non-setting example MDM mobileconfig files in the repo
* Add sysext log stream example, update wording
* Pointed at events and configuration pages for details about logging
* New troubleshooting section
* Standardized on asterisks for page link markup in the TOC index page
2021-11-22 22:22:49 -05:00
Pete Markowsky
acbbb9e7b0 Add a configuration option for users to add their own root labels (#683)
Add an option for users to add their own root labels.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-11-19 10:34:18 -05:00
Kent Ma
3939ad9813 Add santametricservice information to santactl status (#679) 2021-11-16 16:04:59 -05:00
Kent Ma
d20455252d Update santactl fileinfo, sync, and status to show teamID info (#678)
* Update santactl fileinfo, sync, and status to show teamID info
2021-11-16 14:57:02 -05:00
Pete Markowsky
5cd901034f Fixed up typo related to hostname vs. host_name (#676)
Fixed up typo related to hostname vs. host_name.
2021-11-15 15:28:41 -05:00
Kent Ma
4e82392370 Update cli flag for --teamid in santactl (#675) 2021-11-11 15:56:18 +00:00
np5
19710f7233 Do not store Allow TeamID events in the database (#674) 2021-11-11 10:44:39 -05:00
Russell Hancox
27e32bd9ff Tests: Update SNTMetricHTTPWriterTest (#673) 2021-11-11 08:59:14 -05:00
Kent Ma
c268ad4f9a Change SNTEventLog to be a singleton emit a singleton Logger object (#670)
* Change SNTEventLog to be a singleton emit a singleton Logger object
2021-11-10 17:23:01 -05:00
Russell Hancox
f7a1a4cb39 Tests: Fix MetricServiceTest compatible with public OCMock (#669) 2021-11-08 15:53:57 -05:00
Russell Hancox
ad6e03e6cc Tests: Stop using NSInvocation with OCMock's .andDo() (#667) 2021-11-08 12:19:20 -05:00
Russell Hancox
8ecc3f879a Tests: Fix some flaky tests. (#666)
1. OCMock objects don't need stopMocking to be called - it's only necessary to call that in cases where the original object behavior must be restored before the end of the test. Otherwise the mock automatically restores during deallocation.
2. SNTMetricRawJSONFormat still used a plain NSDateFormatter and so was applying timezone calculations. In tests we've switched to using NSISO8601DateFormatter but this requires 10.13 and our deployment target is still 10.9 so I've stuck to applying the UTC timezone to the formatter instead.
2021-11-05 18:03:57 -04:00
Pete Markowsky
d51093501c Fix Flaky Execution Controller Tests (#665)
* Fix up some issues with flaky tests.
2021-11-05 13:51:04 -04:00
np5
05dd1b6215 Add AboutText option for the Santa.app (#662) 2021-11-04 22:02:23 -04:00
Pete Markowsky
8c3320e3e9 Change NSDateFormatter to NSISO8601DateFormatter (#661)
Change NSDateFormatter to NSISO8601DateFormatter.
2021-11-02 13:11:51 -04:00
Tom Burgin
369dc9a63c Add KVO binding for EnableBadSignatureProtection (#659) 2021-10-28 17:34:56 -04:00
Pete Markowsky
7adc55007c Change to NSISO8601DateFormatter to ensure UTC timestamps in unit tests (#658)
Change to NSISO8601DateFormatter to ensure UTC timestamps in unit tests.
2021-10-28 15:34:31 -04:00
Edward Marczak
fe6be921d3 Add EnableBadSignatureProtection key (#656)
Add EnableBadSignatureProtection key and description into the configuration.md doc.
2021-10-28 10:02:24 -04:00
Pete Markowsky
23b31ec413 Add build matrix for build / test steps to shake out OS nuances (#654)
Add build matrix for build / test steps to shake out OS nuances.

Remove macos-latest from build matrix.
2021-10-26 16:14:24 -04:00
Pete Markowsky
727b009a1c Fixed one set of tests. (#652) 2021-10-26 15:36:54 -04:00
Pete Markowsky
1c42f06135 Add Metrics and Metrics Service to Santa (#641)
Add santametricservice and basic metrics to Santad.

This PR adds the santametricservice, and adds basic metrics to santad.  It also updates the SNTMetricSet to have and updates packaging scripts to include the santametricservice (aka metric service) in the final bundle.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-10-26 09:25:10 -04:00
Kent Ma
e1cf8e70a3 Add continuous workflow job for checking for flakes (#650)
Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>
2021-10-25 10:23:42 -04:00
Russell Hancox
7a500b8135 Packaging: Fix syntax error in package_and_sign.sh (#651) 2021-10-22 09:15:20 -04:00
Pete Markowsky
3702af0309 Add description to SNTMetricSet and Fix issues with SNTMetricMonarchJSONFormat (#649)
* Added description to SNTMetricSet and fixed typos in SNTMonarchJSONFormat.
2021-10-21 16:41:24 -04:00
Russell Hancox
697cd29a0a Project: Include package files in release tarball (#648) 2021-10-20 21:31:33 +00:00
Kent Ma
5735a12424 Update list of critical system binaries and include comment about Monterrey behavior (#647) 2021-10-20 16:45:42 -04:00
Russell Hancox
07b8f2121d Project: Include new packaging files in release tarball (#646) 2021-10-20 12:54:00 -04:00
Russell Hancox
78a1a929fd Project: Check-in packaging and signing script. (#645)
This is largely a copy of what we've been using so far but with previously hardcoded stuff replaced with environment variables.
2021-10-20 11:47:30 -04:00
Russell Hancox
9163417b54 santad: enable sysx cache by default (#644)
We've had this enabled long enough now to know that it works correctly and helps performance considerably, so let's have it on by default.
2021-10-18 18:17:11 -04:00
Kent Ma
fa6630a31a Rename shasum to identifier in database (#643) 2021-10-18 13:27:36 -04:00
Kent Ma
1f2b82fc58 Allow banning of team IDs. (#640) 2021-10-18 09:52:56 -04:00
Kent Ma
b77b0142af Add microbenchmark for execs on SNTApplication (#639) 2021-10-15 15:57:04 -04:00
Russell Hancox
2f80a42845 Project: Build driver if files in Source/common/* change (#637) 2021-10-15 15:03:16 +00:00
Russell Hancox
67db370492 Common/Kernel: Add some missing defines to libs included in driver (#638) 2021-10-14 13:05:33 -04:00
Russell Hancox
a0319ecf52 Project: Bump to 2021.8 (#636)
Co-authored-by: Kent Ma <tnek@google.com>
2021-10-13 14:37:44 -04:00
Pete Markowsky
16d0bd6db6 Add Support for Formatting metrics for Monarch (#633)
* Initial commit of a Format that converts SNTMetricSet dictionaries to a format consumable by Monarch tooling.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-10-11 16:01:50 -04:00
Kent Ma
9e3943ec68 Add error on lint failure and include a fix.sh (#632)
Add error on lint failure, include a fix.sh, and fix existing linter errors.
2021-10-11 11:33:10 -04:00
Kent Ma
e461b4bfbc Use direct path in integration_tests.sh instead of relative path (#631) 2021-10-07 13:07:14 -04:00
Russell Hancox
8f836afe86 * Project: Update README and docs/details/santactl (#630)
Re-organized some sections, removed some obsolete statements, fixed a few links.
2021-10-06 17:12:53 -04:00
Russell Hancox
04ad1c34ba Project: Update entitlements files (#629) 2021-10-06 11:36:58 -04:00
Pete Markowsky
c3042e21dc Add a workflow for checking links in Markdown files. (#628) 2021-10-05 19:28:03 -04:00
Russell Hancox
3ede20a121 Project: Fix issues link in README (#626) 2021-10-05 15:49:39 -04:00
Russell Hancox
976118cce4 santactl/sync: Fix tests for santactl/sync (#625)
This test has been around since early 2016 but has been un-runnable since early 2019.
2021-10-05 13:17:50 -04:00
Pete Markowsky
ea85f0f539 Initial commit of an HTTP writer for SNTMetricSets (#624)
* Initial commit of an HTTP writer for SNTMetricSets.

This PR adds support for shipping serialized SNTMetricSets to an HTTP server via POSTs.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-10-04 19:49:40 -04:00
Russell Hancox
d193b05057 Tests: ensure SNTPrefixTree test finishes executing at appropriate time (#623) 2021-10-04 15:41:14 -04:00
Russell Hancox
9fb4f2e171 README: sync servers; remove upvote, sort the rest alphabetically. (#622) 2021-10-04 11:48:13 -04:00
Kent Ma
58cec5819a Add linter step with clang-format and buildifier. (#620)
Also lint our files accordingly
2021-10-01 16:51:06 -04:00
Kent Ma
6ba5831f2d Run buildifier (#619) 2021-10-01 15:18:33 -04:00
Kent Ma
a22e3ead83 Add regular execution integration tests (#618) 2021-10-01 15:07:56 -04:00
Kent Ma
2611b551ce Add provisioningprofile for santactl so that it's properly signed (#617) 2021-10-01 13:00:12 -04:00
Kent Ma
023f96f5c8 Detect existence of a provisionprofile and use that instead in build_and_sign.sh (#616) 2021-10-01 10:07:54 -04:00
239 changed files with 10719 additions and 6504 deletions

1
.bazelversion Normal file
View File

@@ -0,0 +1 @@
5.0.0

View File

@@ -0,0 +1,13 @@
name: Check Markdown links
on:
pull_request:
paths:
- "**.md"
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1

View File

@@ -9,10 +9,9 @@ on:
jobs:
preqs:
runs-on: macos-latest
runs-on: ubuntu-latest
outputs:
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
build_driver: ${{ steps.step1.outputs.build_driver }}
steps:
- uses: actions/checkout@v2
@@ -26,35 +25,36 @@ jobs:
cat files.txt
build_driver=0
build_and_run_tests=0
for file in `cat files.txt`; do
if [[ $file = Source/* ]]; then
build_and_run_test=1;
if [[ $file = Source/santa_driver/* ]]; then
build_driver=1;
break;
fi
fi
done
if [[ $build_and_run_test != 0 ]]; then
if [[ $build_and_run_test != 0 ]]; then
echo "NEED TO RUN BUILD AND TESTS"
echo "::set-output name=run_build_and_tests::true"
else
else
echo "::set-output name=run_build_and_tests::false"
fi
if [[ $build_driver != 0 ]]; then
echo "NEED TO BUILD DRIVER"
echo "::set-output name=build_driver::true"
else
echo "::set-output name=build_driver::false"
fi
lint:
runs-on: ubuntu-latest
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Run linters
run: ./Testing/lint.sh
build_userspace:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11, macos-12]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -62,26 +62,21 @@ jobs:
- name: Build Userspace
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
build_driver:
runs-on: macos-latest
needs: [preqs]
if: needs.preqs.outputs.build_driver == 'true'
steps:
- uses: actions/checkout@v2
- name: Build Driver
run: bazel build --apple_generate_dsym -c opt :release_driver --define=SANTA_BUILD_TYPE=ci
unit_tests:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11, macos-12]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Run All Tests
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci --test_output=errors
test_coverage:
runs-on: macos-latest
runs-on: macos-11
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -92,5 +87,14 @@ jobs:
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./CoverageData/info.lcov
path-to-lcov: ./bazel-out/_coverage/_coverage_report.dat
flag-name: Unit
benchmark:
runs-on: macos-11
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Run All Tests
run: ./Testing/benchmark.sh

13
.github/workflows/continuous.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: continuous
on:
schedule:
- cron: '* 10 * * *' # Every day at 10:00 UTC
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
jobs:
preqs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Checks for flaky tests
run: bazel test --test_strategy=exclusive --test_output=errors --runs_per_test 50 -t- :unit_tests --define=SANTA_BUILD_TYPE=ci

4
.gitignore vendored
View File

@@ -14,3 +14,7 @@ tulsigen-*
*.pem
*.p12
*.keychain
*.swp
compile_commands.json
.cache/
.vscode/*

112
BUILD
View File

@@ -1,6 +1,5 @@
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
package(default_visibility = ["//:santa_package_group"])
@@ -11,8 +10,14 @@ exports_files(["LICENSE"])
# The version label for mac_* rules.
apple_bundle_version(
name = "version",
build_version = SANTA_VERSION,
short_version_string = SANTA_VERSION,
build_label_pattern = ".*santa_{release}\\.{build}",
build_version = "{release}.{build}",
capture_groups = {
"release": "\\d{4}\\.\\d+",
"build": "\\d+",
},
fallback_build_label = "santa_9999.1.1",
short_version_string = "{release}",
)
# Used to detect release builds
@@ -40,7 +45,6 @@ package_group(
packages = ["//..."],
)
################################################################################
# Loading/Unloading/Reloading
################################################################################
@@ -49,7 +53,8 @@ run_command(
cmd = """
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
sudo kextunload -b com.google.santa-driver 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.syncservice.plist 2>/dev/null
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
""",
)
@@ -59,6 +64,8 @@ run_command(
cmd = """
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
launchctl load /Library/LaunchAgents/com.google.santa.plist
""",
)
@@ -67,14 +74,11 @@ run_command(
name = "reload",
srcs = [
"//Source/santa:Santa",
"//Source/santa_driver",
],
cmd = """
set -e
rm -rf /tmp/bazel_santa_reload
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa_driver/santa_driver.zip >/dev/null
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa/Santa.zip >/dev/null
echo "You may be asked for your password for sudo"
@@ -95,15 +99,18 @@ genrule(
"Conf/install.sh",
"Conf/uninstall.sh",
"Conf/com.google.santa.bundleservice.plist",
"Conf/com.google.santa.metricservice.plist",
"Conf/com.google.santa.syncservice.plist",
"Conf/com.google.santad.plist",
"Conf/com.google.santa.plist",
"Conf/com.google.santa.asl.conf",
"Conf/com.google.santa.newsyslog.conf",
"Conf/Package/Makefile",
"Conf/Package/Distribution.xml",
"Conf/Package/notarization_tool.sh",
"Conf/Package/package_and_sign.sh",
"Conf/Package/postinstall",
"Conf/Package/preinstall",
],
outs = ["santa-" + SANTA_VERSION + ".tar.gz"],
outs = ["santa-release.tar.gz"],
cmd = select({
"//conditions:default": """
echo "ERROR: Trying to create a release tarball without optimization."
@@ -120,9 +127,9 @@ genrule(
# Copy config files
for SRC in $(SRCS); do
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
if [[ "$$(dirname $${SRC})" == *"Conf"* ]]; then
mkdir -p $(@D)/conf
cp $${SRC} $(@D)/conf/
cp -H $${SRC} $(@D)/conf/
fi
done
@@ -141,6 +148,14 @@ genrule(
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
;;
*santametricservice.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santametricservice.dSYM
;;
*santasyncservice.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santasyncservice.dSYM
;;
*Santa.app.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
@@ -171,67 +186,20 @@ genrule(
heuristic_label_expansion = 0,
)
genrule(
name = "release_driver",
srcs = [
"//Source/santa_driver",
],
outs = ["santa-driver-" + SANTA_VERSION + ".tar.gz"],
cmd = select({
"//conditions:default": """
echo "ERROR: Trying to create a release tarball without optimization."
echo "Please add '-c opt' flag to bazel invocation"
""",
":opt_build": """
# Extract santa_driver.zip
for SRC in $(SRCS); do
if [ "$$(basename $${SRC})" == "santa_driver.zip" ]; then
mkdir -p $(@D)/binaries
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
fi
done
# Gather together the dSYMs. Throw an error if no dSYMs were found
for SRC in $(SRCS); do
case $${SRC} in
*santa-driver.kext.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santa-driver.kext.dSYM
;;
esac
done
# Cause a build failure if the dSYMs are missing.
if [[ ! -d "$(@D)/dsym" ]]; then
echo "dsym dir missing: Did you forget to use --apple_generate_dsym?"
echo "This flag is required for the 'release' target."
exit 1
fi
# Update all the timestamps to now. Bazel avoids timestamps to allow
# builds to be hermetic and cacheable but for releases we want the
# timestamps to be more-or-less correct.
find $(@D)/{binaries,dsym} -exec touch {} \\;
# Create final output tar
tar -C $(@D) -czpf $(@) binaries dsym
""",
}),
heuristic_label_expansion = 0,
)
test_suite(
name = "unit_tests",
tests = [
"//Source/common:SantaCacheTest",
"//Source/common:SNTFileInfoTest",
"//Source/common:SNTPrefixTreeTest",
"//Source/santactl:SNTCommandFileInfoTest",
"//Source/santactl:SNTCommandSyncTest",
"//Source/santad:SNTApplicationTest",
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
"//Source/santad:SNTRuleTableTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
"//Source/common:unit_tests",
"//Source/santactl:unit_tests",
"//Source/santad:unit_tests",
"//Source/santametricservice:unit_tests",
"//Source/santasyncservice:unit_tests",
],
)
test_suite(
name = "benchmarks",
tests = [
"//Source/santad:SNTApplicationBenchmark",
],
)

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<installer-gui-script minSpecVersion="1">
<title>Santa</title>
<options customize="never" allow-external-scripts="no" hostArchitectures="x86_64,arm64" />
<choices-outline>
<line choice="default" />
</choices-outline>
<choice id="default">
<pkg-ref id="com.google.santa"/>
</choice>
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
</installer-gui-script>

View File

@@ -1,95 +0,0 @@
#
# Package Makefile for Santa
# Requires TheLuggage (github.com/unixorn/luggage) to be installed
#
# Will generate a package based on the latest release. You can replace
# the PACKAGE_VERSION variable with a specific variable instead if you wish.
#
LUGGAGE:=/usr/local/share/luggage/luggage.make
include ${LUGGAGE}
TITLE:=santa
REVERSE_DOMAIN:=com.google
# Get latest Release version using the GitHub API. Each release is bound to a
# git tag, which should always be a semantic version number. The most recent
# release is always first in the API result.
PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
python -c 'import json, sys; print json.load(sys.stdin)[0]["tag_name"]' 2>/dev/null)
# Get the download URL for the latest Release. Each release should have a
# tarball named santa-$version.tar.bz2 containing all of the files associated
# with that release. The tarball layout is:
#
# santa-$version.tar.bz2
# +--santa-$version
# |-- binaries
# | |-- santa-driver.kext
# | |-- Santa.app
# |-- conf
# | |-- install.sh
# | |-- 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
# |-- santad.dSYM
# +-- santactl.dSYM
PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKAGE_VERSION}/santa-${PACKAGE_VERSION}.tar.bz2"
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
pack-applications-Santa.app \
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
santa-driver.kext: download
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))
@curl -fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
@rm -rf *.dSYM
pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
@sudo mkdir -p ${WORK_D}/private/etc/asl
@sudo chown root:wheel ${WORK_D}/private/etc/asl
@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
@sudo chown -R root:wheel ${WORK_D}/Library/Extensions/santa-driver.kext
@sudo chmod -R 755 ${WORK_D}/Library/Extensions/santa-driver.kext
clean: myclean
myclean:
@rm -rf *.dSYM
@rm -rf Santa.app
@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
@rm -f uninstall.sh

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Example NOTARIZATION_TOOL wrapper.
/usr/bin/xcrun altool --notarize-app "${2}" --primary-bundle-id "${4}" \
-u "${NOTARIZATION_USERNAME}" -p "${NOTARIZATION_PASSWORD}"

185
Conf/Package/package_and_sign.sh Executable file
View File

@@ -0,0 +1,185 @@
#!/bin/bash
# This script signs all of Santa's components, verifies the signatures,
# notarizes all of the components, staples them, packages them up, signs the
# package, notarizes the package, puts the package in a DMG and notarizes the
# DMG. It also outputs a single release tarball.
# All of the following environment variables are required.
# RELEASE_ROOT is a required environment variable that points to the root
# of an extracted release tarball produced with the :release and :release_driver
# rules in Santa's main BUILD file.
[[ -n "${RELEASE_ROOT}" ]] || die "RELEASE_ROOT unset"
# SIGNING_IDENTITY, SIGNING_TEAMID and SIGNING_KEYCHAIN are required environment
# variables specifying the identity and keychain to pass to the codesign tool
# and the team ID to use for verification.
[[ -n "${SIGNING_IDENTITY}" ]] || die "SIGNING_IDENTITY unset"
[[ -n "${SIGNING_TEAMID}" ]] || die "SIGNING_TEAMID unset"
[[ -n "${SIGNING_KEYCHAIN}" ]] || die "SIGNING_KEYCHAIN unset"
# INSTALLER_SIGNING_IDENTITY and INSTALLER_SIGNING_KEYCHAIN are required
# environment variables specifying the identity and keychain to use when signing
# the distribution package.
[[ -n "${INSTALLER_SIGNING_IDENTITY}" ]] || die "INSTALLER_SIGNING_IDENTITY unset"
[[ -n "${INSTALLER_SIGNING_KEYCHAIN}" ]] || die "INSTALLER_SIGNING_KEYCHAIN unset"
# NOTARIZATION_TOOL is a required environment variable pointing to a wrapper
# tool around the tool to use for notarization. The tool must take 2 flags:
# --file
# - pointing at a zip file containing the artifact to notarize
# --primary-bundle-id
# - specifying the CFBundleID of the artifact being notarized
[[ -n "${NOTARIZATION_TOOL}" ]] || die "NOTARIZATION_TOOL unset"
# ARTIFACTS_DIR is a required environment variable pointing at a directory to
# place the output artifacts in.
[[ -n "${ARTIFACTS_DIR}" ]] || die "ARTIFACTS_DIR unset"
################################################################################
function die {
echo "${@}"
exit 2
}
readonly INPUT_APP="${RELEASE_ROOT}/binaries/Santa.app"
readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension"
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
readonly APP_PKG_SCRIPTS="${SCRATCH}/pkg_scripts"
readonly ENTITLEMENTS="${SCRATCH}/entitlements"
readonly SCRIPT_PATH="$(/usr/bin/dirname -- ${BASH_SOURCE[0]})"
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
EN="${ENTITLEMENTS}/${BN}.entitlements"
echo "extracting ${BN} entitlements"
/usr/bin/codesign -d --entitlements "${EN}" "${ARTIFACT}"
if [[ -s "${EN}" ]]; then
EN="--entitlements ${EN}"
else
EN=""
fi
echo "codesigning ${BN}"
/usr/bin/codesign --sign "${SIGNING_IDENTITY}" --keychain "${SIGNING_KEYCHAIN}" \
${EN} --timestamp --force --generate-entitlement-der \
--options library,kill,runtime "${ARTIFACT}"
done
# Notarize all the bundles
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
echo "zipping ${BN}"
/usr/bin/zip -9r "${SCRATCH}/${BN}.zip" "${ARTIFACT}"
echo "notarizing ${BN}"
PBID=$(/usr/bin/defaults read "${ARTIFACT}/Contents/Info.plist" CFBundleIdentifier)
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
done
# Staple the App.
for ARTIFACT in "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
echo "stapling ${BN}"
/usr/bin/xcrun stapler staple "${ARTIFACT}"
done
# Ensure _CodeSignature/CodeResources files have 0644 permissions so they can
# be verified without using sudo.
/usr/bin/find "${RELEASE_ROOT}/binaries" -type f -name CodeResources -exec chmod 0644 {} \;
/usr/bin/find "${RELEASE_ROOT}/binaries" -type d -exec chmod 0755 {} \;
/usr/bin/find "${RELEASE_ROOT}/conf" -type f -name "com.google.santa*" -exec chmod 0644 {} \;
echo "verifying signatures"
/usr/bin/codesign -vv -R="certificate leaf[subject.OU] = ${SIGNING_TEAMID}" \
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
echo "creating fresh release tarball"
/bin/mkdir -p "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/binaries" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/conf" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/dsym" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/usr/bin/tar -C "${SCRATCH}/tar_root" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
echo "creating app pkg"
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
"${APP_PKG_ROOT}/Library/LaunchAgents" \
"${APP_PKG_ROOT}/Library/LaunchDaemons" \
"${APP_PKG_ROOT}/private/etc/asl" \
"${APP_PKG_ROOT}/private/etc/newsyslog.d"
/bin/cp -vXR "${RELEASE_ROOT}/binaries/Santa.app" "${APP_PKG_ROOT}/Applications/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santad.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.syncservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"
/bin/cp -vXL "${SCRIPT_PATH}/postinstall" "${APP_PKG_SCRIPTS}/"
/bin/chmod +x "${APP_PKG_SCRIPTS}/"*
# Disable bundle relocation.
/usr/bin/pkgbuild --analyze --root "${APP_PKG_ROOT}" "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleIsRelocatable -bool NO "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleIsVersionChecked -bool NO "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleOverwriteAction -string upgrade "${SCRATCH}/component.plist"
/usr/bin/plutil -replace ChildBundles -json "[]" "${SCRATCH}/component.plist"
# Build app package
/usr/bin/pkgbuild --identifier "com.google.santa" \
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
--root "${APP_PKG_ROOT}" \
--component-plist "${SCRATCH}/component.plist" \
--scripts "${APP_PKG_SCRIPTS}" \
"${SCRATCH}/app.pkg"
# Build signed distribution package
echo "productbuild pkg"
/bin/mkdir -p "${SCRATCH}/${RELEASE_NAME}"
/usr/bin/productbuild \
--distribution "${SCRIPT_PATH}/Distribution.xml" \
--package-path "${SCRATCH}" \
--sign "${INSTALLER_SIGNING_IDENTITY}" --keychain "${INSTALLER_SIGNING_KEYCHAIN}" \
"${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg"
echo "verifying pkg signature"
/usr/sbin/pkgutil --check-signature "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "bad pkg signature"
echo "notarizing pkg"
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" \
--primary-bundle-id "com.google.santa"
echo "stapling pkg"
/usr/bin/xcrun stapler staple "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "failed to staple pkg"
echo "wrapping pkg in dmg"
/usr/bin/hdiutil create -fs HFS+ -format UDZO \
-volname "${RELEASE_NAME}" \
-ov -imagekey zlib-level=9 \
-srcfolder "${SCRATCH}/${RELEASE_NAME}/" "${DMG_PATH}" || die "failed to wrap pkg in dmg"
echo "notarizing dmg"
"${NOTARIZATION_TOOL}" --file "${DMG_PATH}" --primary-bundle-id "com.google.santa"
echo "stapling dmg"
/usr/bin/xcrun stapler staple "${DMG_PATH}" || die "failed to staple dmg"

View File

@@ -14,7 +14,6 @@ mkdir -p /usr/local/bin
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
# Remove the kext before com.google.santa.daemon loads if the SystemExtension is already present.
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && rm -rf /Library/Extensions/santa-driver.kext
# Load com.google.santa.daemon, its main has logic to handle loading the kext
@@ -24,6 +23,12 @@ mkdir -p /usr/local/bin
# Load com.google.santa.bundleservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load com.google.santa.metricservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load com.google.santa.syncservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.syncservice.plist
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
[[ -z "${GUI_USER}" ]] && exit 0

View File

@@ -8,6 +8,8 @@
/bin/launchctl remove com.google.santad || true
/bin/launchctl remove com.google.santa.bundleservice || true
/bin/launchctl remove com.google.santa.metricservice || true
/bin/launchctl remove com.google.santa.syncservice || true
/bin/sleep 1

View File

@@ -1,6 +0,0 @@
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
> /var/db/santa/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
? [= Sender kernel] [S= Message santa-driver:] claim
? [= Sender kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/db/santa/santa.log

View File

@@ -0,0 +1,22 @@
<?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>Label</key>
<string>com.google.santa.metricservice</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/santametricservice</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>com.google.santa.metricservice</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@@ -1,2 +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
/var/db/santa/santa.log root:wheel 644 10 25000 * Z

View File

@@ -15,8 +15,8 @@
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<false/>
<key>KeepAlive</key>
<true/>
<false/>
</dict>
</plist>

View File

@@ -24,6 +24,12 @@ fi
# Unload bundle service
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
# Unload metric service
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
# Unload sync service
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
@@ -43,23 +49,21 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
/bin/rm -rf /Applications/Santa.app 2>&1
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
/bin/rm /etc/asl/com.google.santa.asl.conf
# Copy new files.
/bin/mkdir -p /var/db/santa
/bin/cp -r ${BINARIES}/Santa.app /Applications
# Only copy the kext if the SystemExtension is not present.
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 || /bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions && /usr/sbin/kextcache -update-volume / -bundle-id com.google.santa-driver
/bin/mkdir -p /usr/local/bin
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
# Reload syslogd to pick up ASL configuration change.
@@ -71,6 +75,12 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Load com.google.santa.bundleservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load com.google.santa.metricservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load com.google.santa.syncservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
# Load GUI agent if someone is logged in.
[[ -z "${GUI_USER}" ]] && exit 0

View File

@@ -10,6 +10,10 @@
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && /Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
/bin/launchctl remove com.google.santad
# remove helper XPC services
/bin/launchctl remove com.google.santa.bundleservice
/bin/launchctl remove com.google.santa.metricservice
/bin/launchctl remove com.google.santa.syncservice
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
user=$(/usr/bin/stat -f '%u' /dev/console)
@@ -24,6 +28,8 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.syncservice.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

View File

@@ -16,10 +16,10 @@
#include <iostream>
#include <vector>
#include <SNTCommandSyncConstants.h>
#include <SNTCommandSyncRuleDownload.h>
#include <SNTCommandSyncState.h>
#include <SNTRule.h>
#include <SNTSyncConstants.h>
#include <SNTSyncRuleDownload.h>
#include <SNTSyncState.h>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
@@ -41,12 +41,12 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
return 0;
}
SNTCommandSyncState *state = [[SNTCommandSyncState alloc] init];
SNTSyncState *state = [[SNTSyncState alloc] init];
if (!state) {
return 0;
}
SNTCommandSyncRuleDownload *obj = [[SNTCommandSyncRuleDownload alloc] initWithState:state];
SNTSyncRuleDownload *obj = [[SNTSyncRuleDownload alloc] initWithState:state];
if (!obj) {
return 0;
}

View File

@@ -46,7 +46,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = (SNTRuleState)input_data.state;
newRule.type = (SNTRuleType)input_data.type;
newRule.shasum = @(input_data.hash);
newRule.identifier = @(input_data.hash);
newRule.customMsg = @"";
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
@@ -62,9 +62,9 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
reply:^(NSError *error) {
if (!error) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Removed rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Added rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
}
}
}];

View File

@@ -1,25 +1,22 @@
# Santa [![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/google/santa/badge.svg?branch=main)](https://coveralls.io/github/google/santa?branch=main)
<p align="center">
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
<img src="https://raw.githubusercontent.com/google/santa/main/Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
</p>
Santa is a binary authorization system for macOS. It consists of a kernel
extension (or a system extension on macOS 10.15+) that monitors for executions,
a userland daemon that makes execution decisions based on the contents of a
SQLite database, a GUI agent that notifies the user in case of a block decision
and a command-line utility for managing the system and synchronizing the
database with a server.
Santa is a binary authorization system for macOS. It consists of a system
extension that monitors for executions, a daemon that makes execution decisions
based on the contents of a local 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.
It is named Santa because it keeps track of binaries that are naughty or nice.
Santa is a project of Google's Macintosh Operations Team.
# Docs
The Santa docs are stored in the
[Docs](https://github.com/google/santa/blob/master/docs) directory. The docs are
published at http://santa.dev.
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
at http://santa.dev.
The docs include deployment options, details on how parts of Santa work and
instructions for developing Santa itself.
@@ -31,14 +28,14 @@ the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
great place.
If you believe you have a bug, feel free to report [an
issue](https://github.com/google/santa/isues) and we'll respond as soon as we
issue](https://github.com/google/santa/issues) and we'll respond as soon as we
can.
If you believe you've found a vulnerability, please read the
[security policy](https://github.com/google/santa/security/policy) for
disclosure reporting.
# Admin-Related Features
# Features
* Multiple modes: In the default MONITOR mode, all binaries except those marked
as blocked will be allowed to run, whilst being logged and recorded in
@@ -74,6 +71,14 @@ disclosure reporting.
common apps. Likewise, you cannot block Santa itself, and Santa uses a
distinct separate cert than other Google apps.
* Userland components validate each other: each of the userland components (the
daemon, the GUI agent and the command-line utility) communicate with each
other using XPC and check that their signing certificates are identical
before any communication is accepted.
* Caching: allowed binaries are cached so the processing required to make a
request is only done if the binary isn't already cached.
# Intentions and Expectations
No single system or process will stop *all* attacks, or provide 100% security.
@@ -89,36 +94,11 @@ protect hosts in whatever other ways you see fit.
# Security and Performance-Related Features
* In-kernel caching: allowed binaries are cached in the kernel so the
processing required to make a request is only done if the binary isn't
already cached.
* Userland components validate each other: each of the userland components (the
daemon, the GUI agent and the command-line utility) communicate with each
other using XPC and check that their signing certificates are identical
before any communication is accepted.
* Kext uses only KPIs: the kernel extension only uses provided kernel
programming interfaces to do its job. This means that the kext code should
continue to work across OS versions.
# Known Issues
* Santa only blocks execution (execve and variants), it doesn't protect against
dynamic libraries loaded with dlopen, libraries on disk that have been
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version
0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were
exploited in some versions of macOS. We are working on also protecting
against similar avenues of attack.
* Kext communication security: the kext will only accept a connection from a
single client at a time and said client must be running as root. We haven't
yet found a good way to ensure the kext only accepts connections from a valid
client.
* Database protection: the SQLite database is installed with permissions so
that only the root user can read/write it. We're considering approaches to
secure this further.
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`.
* Scripts: Santa is currently written to ignore any execution that isn't a
binary. This is because after weighing the administration cost vs the
@@ -133,19 +113,17 @@ protect hosts in whatever other ways you see fit.
management server, which uploads events that have occurred on the machine and
downloads new rules. There are several open-source servers you can sync with:
* [Upvote](https://github.com/google/upvote) - An AppEngine-based server
that implements social voting to make managing a large fleet easier.
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
serves hardcoded rules from simple configuration files.
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
centralized service that pulls data from multiple sources and deploy
configurations to multiple services.
* [Zercurity](https://github.com/zercurity/zercurity) - A dockerized service
for managing and monitoring applications across a large fleet utilizing
Santa + Osquery.
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
* Alternatively, `santactl` can configure rules locally (without a sync
server).
@@ -158,31 +136,9 @@ instead.
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
# Kext Signing
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
Developer ID certificate with a kernel extension flag. Without it, the only way
to load an extension is to enable kext-dev-mode or disable SIP, depending on
the OS version.
There are two possible solutions for this, for distribution purposes:
1) Use a [pre-built, pre-signed
version](https://github.com/google/santa/releases) of the kext that we supply.
Each time changes are made to the kext code we will update the pre-built
version that you can make use of. This doesn't prevent you from making changes
to the non-kext parts of Santa and distributing those. If you make changes to
the kext and make a pull request, we can merge them in and distribute a new
version of the pre-signed kext.
2) Apply for your own [kext signing
certificate](https://developer.apple.com/contact/kext/). Apple will only grant
this for broad distribution within an organization, they won't issue them just
for testing purposes.
# Contributing
Patches to this project are very much welcome. Please see the
[CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
file.
[CONTRIBUTING](https://santa.dev/development/contributing) doc.
# Disclaimer
This is **not** an official Google product.

View File

@@ -1,13 +1,32 @@
load("//:helper.bzl", "santa_unit_test")
load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
package(default_visibility = ["//:santa_package_group"])
package(
default_visibility = ["//:santa_package_group"],
)
licenses(["notice"])
proto_library(
name = "santa_proto",
srcs = ["santa.proto"],
deps = [
"@com_google_protobuf//:any_proto",
"@com_google_protobuf//:timestamp_proto",
],
)
objc_proto_library(
name = "santa_objc_proto",
copts = ["-fno-objc-arc"],
non_arc_srcs = ["Santa.pbobjc.m"],
protos = [":santa_proto"],
)
cc_library(
name = "SantaCache",
hdrs = ["SantaCache.h"],
deps = ["//Source/common:SNTKernelCommon"],
deps = ["//Source/common:SNTCommon"],
)
santa_unit_test(
@@ -16,7 +35,7 @@ santa_unit_test(
"SantaCache.h",
"SantaCacheTest.mm",
],
deps = ["//Source/common:SNTKernelCommon"],
deps = ["//Source/common:SNTCommon"],
)
objc_library(
@@ -27,6 +46,7 @@ objc_library(
":SNTConfigurator",
":SNTLogging",
":SNTStoredEvent",
":SNTSystemInfo",
],
)
@@ -37,8 +57,10 @@ objc_library(
defines = ["SANTAGUI"],
deps = [
":SNTConfigurator",
":SNTDeviceEvent",
":SNTLogging",
":SNTStoredEvent",
":SNTSystemInfo",
],
)
@@ -47,11 +69,26 @@ objc_library(
srcs = ["SNTCachedDecision.m"],
hdrs = ["SNTCachedDecision.h"],
deps = [
":SNTCommon",
":SNTCommonEnums",
":SNTKernelCommon",
],
)
objc_library(
name = "SNTDeviceEvent",
srcs = ["SNTDeviceEvent.m"],
hdrs = ["SNTDeviceEvent.h"],
deps = [
":SNTCommonEnums",
],
)
objc_library(
name = "SNTAllowlistInfo",
srcs = ["SNTAllowlistInfo.m"],
hdrs = ["SNTAllowlistInfo.h"],
)
objc_library(
name = "SNTCommonEnums",
hdrs = ["SNTCommonEnums.h"],
@@ -85,18 +122,12 @@ objc_library(
)
cc_library(
name = "SNTKernelCommon",
hdrs = ["SNTKernelCommon.h"],
)
cc_library(
name = "SNTLoggingKernel",
hdrs = ["SNTLogging.h"],
copts = [
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
name = "SNTCommon",
hdrs = ["SNTCommon.h"],
defines = [
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
defines = ["KERNEL"],
)
objc_library(
@@ -114,19 +145,6 @@ cc_library(
deps = [":SNTLogging"],
)
cc_library(
name = "SNTPrefixTreeKernel",
srcs = ["SNTPrefixTree.cc"],
hdrs = ["SNTPrefixTree.h"],
copts = [
"-std=c++11",
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = ["KERNEL"],
deps = [":SNTLoggingKernel"],
)
objc_library(
name = "SNTRule",
srcs = ["SNTRule.m"],
@@ -180,9 +198,12 @@ objc_library(
srcs = ["SNTXPCControlInterface.m"],
hdrs = ["SNTXPCControlInterface.h"],
deps = [
":SNTCommonEnums",
":SNTConfigurator",
":SNTRule",
":SNTStoredEvent",
":SNTXPCUnprivilegedControlInterface",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
@@ -201,16 +222,7 @@ objc_library(
name = "SNTMetricSet",
srcs = ["SNTMetricSet.m"],
hdrs = ["SNTMetricSet.h"],
)
objc_library(
name = "SNTXPCSyncdInterface",
srcs = ["SNTXPCSyncdInterface.m"],
hdrs = ["SNTXPCSyncdInterface.h"],
deps = [
":SNTCommonEnums",
":SNTStoredEvent",
],
deps = [":SNTCommonEnums"],
)
objc_library(
@@ -218,6 +230,7 @@ objc_library(
srcs = ["SNTXPCSyncServiceInterface.m"],
hdrs = ["SNTXPCSyncServiceInterface.h"],
deps = [
":SNTCommonEnums",
":SNTStoredEvent",
"@MOLXPCConnection",
],
@@ -228,8 +241,8 @@ objc_library(
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
deps = [
":SNTCommon",
":SNTCommonEnums",
":SNTKernelCommon",
":SNTRule",
":SNTStoredEvent",
":SNTXPCBundleServiceInterface",
@@ -242,9 +255,9 @@ santa_unit_test(
name = "SNTFileInfoTest",
srcs = ["SNTFileInfoTest.m"],
resources = [
"testdata/32bitplist",
"testdata/bad_pagezero",
"testdata/missing_pagezero",
"testdata/32bitplist",
],
structured_resources = glob([
"testdata/BundleExample.app/**",
@@ -264,3 +277,14 @@ santa_unit_test(
srcs = ["SNTMetricSetTest.m"],
deps = [":SNTMetricSet"],
)
test_suite(
name = "unit_tests",
tests = [
":SNTFileInfoTest",
":SNTMetricSetTest",
":SNTPrefixTreeTest",
":SantaCacheTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -0,0 +1,32 @@
/// Copyright 2021 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/Foundation.h>
///
/// Store information about new allowlist rules for later logging.
///
@interface SNTAllowlistInfo : NSObject
@property pid_t pid;
@property int pidversion;
@property NSString *targetPath;
@property NSString *sha256;
- (instancetype)initWithPid:(pid_t)pid
pidversion:(int)pidver
targetPath:(NSString*)targetPath
sha256:(NSString*)hash;
@end

View File

@@ -0,0 +1,32 @@
/// Copyright 2021 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 "Source/common/SNTAllowlistInfo.h"
@implementation SNTAllowlistInfo
- (instancetype)initWithPid:(pid_t)pid
pidversion:(int)pidver
targetPath:(NSString *)targetPath
sha256:(NSString *)hash {
self = [super init];
if (self) {
_pid = pid;
_pidversion = pidver;
_targetPath = targetPath;
_sha256 = hash;
}
return self;
}
@end

View File

@@ -24,12 +24,17 @@
///
/// Return a message suitable for presenting to the user.
/// Uses either the configured message depending on the event type or a custom message
/// if the rule that blocked this file included one.
///
/// In SantaGUI this will return an NSAttributedString with links and formatting included
/// while for santad all HTML will be properly stripped.
///
+ (NSAttributedString *)formatMessage:(NSString *)message;
///
/// Uses either the configured message depending on the event type or a custom message
/// if the rule that blocked this file included one, formatted using
/// +[SNTBlockMessage formatMessage].
///
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage;

View File

@@ -21,8 +21,7 @@
@implementation SNTBlockMessage
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage {
+ (NSAttributedString *)formatMessage:(NSString *)message {
NSString *htmlHeader =
@"<html><head><style>"
@"body {"
@@ -48,6 +47,22 @@
NSString *htmlFooter = @"</body></html>";
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
#ifdef SANTAGUI
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
#else
NSString *strippedHTML = [self stringFromHTML:fullHTML];
if (!strippedHTML) {
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
}
return [[NSAttributedString alloc] initWithString:strippedHTML];
#endif
}
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage {
NSString *message;
if (customMessage.length) {
message = customMessage;
@@ -64,19 +79,7 @@
@"because it has been deemed malicious.";
}
}
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
#ifdef SANTAGUI
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
#else
NSString *strippedHTML = [self stringFromHTML:fullHTML];
if (!strippedHTML) {
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
}
return [[NSAttributedString alloc] initWithString:strippedHTML];
#endif
return [SNTBlockMessage formatMessage:message];
}
+ (NSString *)stringFromHTML:(NSString *)html {
@@ -116,9 +119,16 @@
if (!formatStr.length) return nil;
if (event.fileSHA256) {
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_identifier%"
withString:event.fileSHA256];
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%bundle_or_file_identifier%"
withString:event.fileBundleHash ?: event.fileSHA256];
}
if (event.executingUser) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"

View File

@@ -15,7 +15,7 @@
#import <Foundation/Foundation.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/common/SNTCommon.h"
@class MOLCertificate;
@@ -32,6 +32,7 @@
@property NSString *certSHA256;
@property NSString *certCommonName;
@property NSArray<MOLCertificate *> *certChain;
@property NSString *teamID;
@property NSString *quarantineURL;

View File

@@ -13,50 +13,19 @@
/// limitations under the License.
///
/// Common defines between kernel <-> userspace
/// Common defines between daemon <-> client
///
#ifndef SANTA__COMMON__KERNELCOMMON_H
#define SANTA__COMMON__KERNELCOMMON_H
#ifndef SANTA__COMMON__COMMON_H
#define SANTA__COMMON__COMMON_H
#include <stdint.h>
#include <sys/param.h>
// Defines the name of the userclient class and the driver bundle ID.
#define USERCLIENT_CLASS "com_google_SantaDriver"
#define USERCLIENT_ID "com.google.santa-driver"
// Branch prediction
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// List of methods supported by the driver.
enum SantaDriverMethods {
kSantaUserClientOpen,
kSantaUserClientAllowBinary,
kSantaUserClientAllowCompiler,
kSantaUserClientDenyBinary,
kSantaUserClientAcknowledgeBinary,
kSantaUserClientClearCache,
kSantaUserClientRemoveCacheEntry,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
kSantaUserClientCacheBucketCount,
kSantaUserClientFilemodPrefixFilterAdd,
kSantaUserClientFilemodPrefixFilterReset,
// Any methods supported by the driver should be added above this line to
// ensure this remains the count of methods.
kSantaUserClientNMethods,
};
typedef enum {
QUEUETYPE_DECISION,
QUEUETYPE_LOG,
} santa_queuetype_t;
// Enum defining actions that can be passed down the IODataQueue and in
// response methods.
typedef enum {
ACTION_UNSET = 0,
@@ -103,15 +72,9 @@ typedef struct santa_vnode_id_t {
bool operator==(const santa_vnode_id_t &rhs) const {
return fsid == rhs.fsid && fileid == rhs.fileid;
}
// This _must not_ be used for anything security-sensitive. It exists solely
// to make the msleep/wakeup calls easier.
uint64_t unsafe_simple_id() const {
return (((uint64_t)fsid << 32) | fileid);
}
#endif
} santa_vnode_id_t;
// Message struct that is sent down the IODataQueue.
typedef struct {
santa_action_t action;
santa_vnode_id_t vnode_id;
@@ -122,25 +85,18 @@ typedef struct {
pid_t ppid;
char path[MAXPATHLEN];
char newpath[MAXPATHLEN];
char ttypath[MAXPATHLEN];
// For file events, this is the process name.
// For exec requests, this is the parent process name.
// While process names can technically be 4*MAXPATHLEN, that never
// actually happens, so only take MAXPATHLEN and throw away any excess.
char pname[MAXPATHLEN];
// For messages that originate from EndpointSecurity, this points to a copy of
// the message.
// This points to a copy of the original ES message.
void *es_message;
// For messages that originate from EndpointSecurity, this points to an
// NSArray of the arguments.
// This points to an NSArray of the process arguments.
void *args_array;
} santa_message_t;
// Used for the kSantaUserClientCacheBucketCount request.
typedef struct {
uint16_t per_bucket[1024];
uint64_t start;
} santa_bucket_count_t;
#endif // SANTA__COMMON__KERNELCOMMON_H
#endif // SANTA__COMMON__COMMON_H

View File

@@ -24,6 +24,7 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {
SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
SNTRuleTypeTeamID = 3,
};
typedef NS_ENUM(NSInteger, SNTRuleState) {
@@ -55,6 +56,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateBlockBinary = 1 << 17,
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
SNTEventStateBlockTeamID = 1 << 20,
// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
@@ -64,6 +66,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
SNTEventStateAllowTeamID = 1 << 31,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
@@ -89,15 +92,31 @@ typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
typedef NS_ENUM(NSInteger, SNTEventLogType) {
SNTEventLogTypeSyslog,
SNTEventLogTypeFilelog,
SNTEventLogTypeProtobuf,
SNTEventLogTypeNull,
};
// The return status of a sync.
typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
SNTSyncStatusTypeSuccess,
SNTSyncStatusTypePreflightFailed,
SNTSyncStatusTypeEventUploadFailed,
SNTSyncStatusTypeRuleDownloadFailed,
SNTSyncStatusTypePostflightFailed,
SNTSyncStatusTypeTooManySyncsInProgress,
SNTSyncStatusTypeMissingSyncBaseURL,
SNTSyncStatusTypeMissingMachineID,
SNTSyncStatusTypeDaemonTimeout,
SNTSyncStatusTypeSyncStarted,
SNTSyncStatusTypeUnknown,
};
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeUnknown,
SNTMetricFormatTypeRawJSON,
SNTMetricFormatTypeJSON,
SNTMetricFormatTypeMonarchJSON,
};
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
static const char *kSantaDPath =
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";

View File

@@ -26,7 +26,7 @@
#pragma mark - Daemon Settings
///
/// The operating mode.
/// The operating mode. Defaults to MONITOR.
///
@property(readonly, nonatomic) SNTClientMode clientMode;
@@ -35,6 +35,17 @@
///
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
///
/// Enable Fail Close mode. Defaults to NO.
/// This controls Santa's behavior when a failure occurs, such as an
/// inability to read a file. By default, to prevent bugs or misconfiguration
/// from rendering a machine inoperable Santa will fail open and allow
/// execution. With this setting enabled, Santa will fail closed if the client
/// is in LOCKDOWN mode, offering a higher level of security but with a higher
/// potential for causing problems.
///
@property(readonly, nonatomic) BOOL failClosed;
///
/// The regex of allowed paths. Regexes are specified in ICU format.
///
@@ -140,8 +151,13 @@
///
/// 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.
/// SNTEventLogTypeSyslog "syslog": Sent to ASL or ULS (if built with the 10.12 SDK or later).
/// SNTEventLogTypeFilelog "file": Sent to a file on disk. Use eventLogPath to specify a path.
/// SNTEventLogTypeNull "null": Logs nothing
/// SNTEventLogTypeProtobuf "protobuf": (BETA) Sent to a file on disk, using maildir format. Use
/// mailDirectory to specify a path. Use mailDirectoryFileSizeThresholdKB,
/// mailDirectorySizeThresholdMB and mailDirectoryEventMaxFlushTimeSec to configure
/// additional maildir format settings.
/// Defaults to SNTEventLogTypeFilelog.
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
///
@@ -157,6 +173,42 @@
///
@property(readonly, nonatomic) NSString *eventLogPath;
///
/// If eventLogType is set to protobuf, mailDirectory will provide the base path used for
/// saving logs using the maildir format.
/// Defaults to /var/db/santa/mail.
///
/// @note: This property is KVO compliant, but should only be read once at santad startup.
///
@property(readonly, nonatomic) NSString *mailDirectory;
///
/// If eventLogType is set to protobuf, mailDirectoryFileSizeThresholdKB sets the per-file size
/// limit for files saved in the mailDirectory.
/// Defaults to 100.
///
/// @note: This property is KVO compliant, but should only be read once at santad startup.
///
@property(readonly, nonatomic) NSUInteger mailDirectoryFileSizeThresholdKB;
///
/// If eventLogType is set to protobuf, mailDirectorySizeThresholdMB sets the total size
/// limit for all files saved in the mailDirectory.
/// Defaults to 500.
///
/// @note: This property is KVO compliant, but should only be read once at santad startup.
///
@property(readonly, nonatomic) NSUInteger mailDirectorySizeThresholdMB;
///
/// If eventLogType is set to protobuf, mailDirectoryEventMaxFlushTimeSec sets the maximum amount
/// of time an event will be stored in memory before being written to disk.
/// Defaults to 5.0.
///
/// @note: This property is KVO compliant, but should only be read once at santad startup.
///
@property(readonly, nonatomic) float mailDirectoryEventMaxFlushTimeSec;
///
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
/// has been overriden, this is the host's UUID.
@@ -164,16 +216,6 @@
///
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
///
/// Use the bundled SystemExtension on macOS 10.15+, defaults to YES.
/// Disable to continue using the bundled KEXT.
/// This is a one way switch, if this is ever true on macOS 10.15+ the KEXT will be deleted.
/// This gives admins control over the timing of switching to the SystemExtension. The intended use
/// case is to have an MDM deliver the requisite SystemExtension and TCC profiles before attempting
/// to load.
///
@property(readonly, nonatomic) BOOL enableSystemExtension;
///
/// Use an internal cache for decisions instead of relying on the caching
/// mechanism built-in to the EndpointSecurity framework. This may increase
@@ -185,6 +227,12 @@
#pragma mark - GUI Settings
///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
///
@property(readonly, nonatomic) NSString *aboutText;
///
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
/// If unset, the button will not be displayed.
@@ -229,6 +277,20 @@
///
@property(readonly, nonatomic) NSString *bannedBlockMessage;
///
/// This is the message shown to the user when a USB storage device's mount is denied
/// from the BlockUSB configuration setting. If not configured, a reasonable
/// default is provided.
///
@property(readonly, nonatomic) NSString *bannedUSBBlockMessage;
///
/// This is the message shown to the user when a USB storage device's mount is forcibly
/// remounted to a different set of permissions from the BlockUSB and RemountUSBMode
/// configuration settings. If not configured, a reasonable default is provided.
///
@property(readonly, nonatomic) NSString *remountUSBBlockMessage;
///
/// The notification text to display when the client goes into MONITOR mode.
/// Defaults to "Switching into Monitor mode"
@@ -248,6 +310,14 @@
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// Proxy settings for syncing.
/// This dictionary is passed directly to NSURLSession. The allowed keys
/// are loosely documented at
/// https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants.
///
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
///
/// The machine owner.
///
@@ -268,6 +338,23 @@
///
@property(nonatomic) BOOL syncCleanRequired;
///
/// USB Mount Blocking. Defaults to false.
///
@property(nonatomic) BOOL blockUSBMount;
///
/// Comma-seperated `$ mount -o` arguments used for forced remounting of USB devices. Default
/// to fully allow/deny without remounting if unset.
///
@property(nonatomic) NSArray<NSString *> *remountUSBMode;
///
/// When `blockUSBMount` is set, this is the message shown to the user when a device is blocked
/// If this message is not configured, a reasonable default is provided.
///
@property(readonly, nonatomic) NSString *usbBlockMessage;
///
/// If set, this over-rides the default machine ID used for syncing.
///
@@ -325,6 +412,17 @@
///
@property(readonly, nonatomic) NSString *syncClientAuthCertificateIssuer;
///
/// If true, syncs will upload events when a clean sync is requested. Defaults to false.
///
@property(readonly, nonatomic) BOOL enableCleanSyncEventUpload;
///
/// If true, events will be uploaded for all executions, even those that are allowed.
/// Use with caution, this generates a lot of events. Defaults to false.
///
@property(nonatomic) BOOL enableAllEventUpload;
///
/// If true, forks and exits will be logged. Defaults to false.
///
@@ -386,6 +484,20 @@
///
@property(readonly, nonatomic) NSURL *metricURL;
///
/// Extra Metric Labels to add to the metrics payloads.
///
@property(readonly, nonatomic) NSDictionary *extraMetricLabels;
///
/// Duration in seconds of how often the metrics should be exported.
///
@property(readonly, nonatomic) NSUInteger metricExportInterval;
///
/// Duration in seconds for metrics export timeout. Defaults to 30;
///
@property(readonly, nonatomic) NSUInteger metricExportTimeout;
///
/// Retrieve an initialized singleton configurator object using the default file path.

View File

@@ -28,7 +28,7 @@
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
/// Holds the configurations from a sync server and mobileconfig.
@property NSMutableDictionary *syncState;
@property NSDictionary *syncState;
@property NSMutableDictionary *configState;
/// Was --debug passed as an argument to this process?
@@ -45,6 +45,8 @@ static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The keys managed by a mobileconfig.
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kSyncProxyConfigKey = @"SyncProxyConfiguration";
static NSString *const kSyncEnableCleanSyncEventUpload = @"SyncEnableCleanSyncEventUpload";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
@@ -59,11 +61,15 @@ static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kAboutText = @"AboutText";
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 kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
static NSString *const kRemountUSBBlockMessage = @"RemountUSBBlockMessage";
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
@@ -75,10 +81,13 @@ static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters
static NSString *const kEventLogType = @"EventLogType";
static NSString *const kEventLogPath = @"EventLogPath";
static NSString *const kMailDirectory = @"MailDirectory";
static NSString *const kMailDirectoryFileSizeThresholdKB = @"MailDirectoryFileSizeThresholdKB";
static NSString *const kMailDirectorySizeThresholdMB = @"MailDirectorySizeThresholdMB";
static NSString *const kMailDirectoryEventMaxFlushTimeSec = @"MailDirectoryEventMaxFlushTimeSec";
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
static NSString *const kEnableSystemExtension = @"EnableSystemExtension";
static NSString *const kEnableSysxCache = @"EnableSysxCache";
static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
@@ -94,16 +103,23 @@ static NSString *const kFCMAPIKey = @"FCMAPIKey";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kFailClosedKey = @"FailClosed";
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting";
static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
static NSString *const kAllowedPathRegexKeyDeprecated = @"WhitelistRegex";
static NSString *const kBlockedPathRegexKey = @"BlockedPathRegex";
static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
static NSString *const kEnableAllEventUploadKey = @"EnableAllEventUpload";
// TODO(markowsky): move these to sync server only.
static NSString *const kMetricFormat = @"MetricFormat";
static NSString *const kMetricURL = @"MetricURL";
static NSString *const kMetricExportInterval = @"MetricExportInterval";
static NSString *const kMetricExportTimeout = @"MetricExportTimeout";
static NSString *const kMetricExtraLabels = @"MetricExtraLabels";
// The keys managed by a sync server.
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
@@ -119,6 +135,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
Class string = [NSString class];
Class data = [NSData class];
Class array = [NSArray class];
Class dictionary = [NSDictionary class];
_syncServerKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveRulesKey : number,
@@ -127,12 +144,16 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
kSyncCleanRequired : number,
kEnableAllEventUploadKey : number,
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
kFailClosedKey : number,
kEnableTransitiveRulesKey : number,
kEnableTransitiveRulesKeyDeprecated : number,
kFileChangesRegexKey : re,
@@ -141,16 +162,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kAboutText : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
kUnknownBlockMessage : string,
kBannedBlockMessage : string,
kBannedUSBBlockMessage : string,
kRemountUSBBlockMessage : string,
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kSyncBaseURLKey : string,
kSyncProxyConfigKey : dictionary,
kClientAuthCertificateFileKey : string,
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
@@ -165,8 +192,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kMachineIDPlistKeyKey : string,
kEventLogType : string,
kEventLogPath : string,
kMailDirectory : string,
kMailDirectoryFileSizeThresholdKB : number,
kMailDirectorySizeThresholdMB : number,
kMailDirectoryEventMaxFlushTimeSec : number,
kEnableMachineIDDecoration : number,
kEnableSystemExtension : number,
kEnableSysxCache : number,
kEnableForkAndExitLogging : number,
kIgnoreOtherEndpointSecurityClients : number,
@@ -177,6 +207,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kFCMAPIKey : string,
kMetricFormat : string,
kMetricURL : string,
kMetricExportInterval : number,
kMetricExportTimeout : number,
kMetricExtraLabels : dictionary,
kEnableAllEventUploadKey : number,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -256,6 +290,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingAboutText {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [self configStateSet];
}
@@ -336,6 +374,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMailDirectory {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryFileSizeThresholdKB {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMailDirectorySizeThresholdMB {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryEventMaxFlushTimeSec {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
return [self configStateSet];
}
@@ -344,8 +398,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
return [self configStateSet];
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
@@ -384,6 +438,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBadSignatureProtection {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -406,6 +464,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
- (BOOL)failClosed {
NSNumber *n = self.configState[kFailClosedKey];
if (n) return [n boolValue];
return NO;
}
- (BOOL)enableTransitiveRules {
NSNumber *n = self.syncState[kEnableTransitiveRulesKey];
if (n) return [n boolValue];
@@ -471,6 +535,20 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return filters;
}
- (void)setRemountUSBMode:(NSArray<NSString *> *)args {
[self updateSyncStateForKey:kRemountUSBModeKey value:args];
}
- (NSArray<NSString *> *)remountUSBMode {
NSArray<NSString *> *args = self.configState[kRemountUSBModeKey];
for (id arg in args) {
if (![arg isKindOfClass:[NSString class]]) {
return nil;
}
}
return args;
}
- (NSURL *)syncBaseURL {
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
@@ -478,6 +556,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return url;
}
- (NSDictionary *)syncProxyConfig {
return self.configState[kSyncProxyConfigKey];
}
- (BOOL)enablePageZeroProtection {
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
return number ? [number boolValue] : YES;
@@ -488,6 +570,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : NO;
}
- (NSString *)aboutText {
return self.configState[kAboutText];
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
}
@@ -508,6 +594,21 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return self.configState[kBannedBlockMessage];
}
- (NSString *)bannedUSBBlockMessage {
if (!self.configState[kBannedUSBBlockMessage]) {
return @"The following device has been blocked from mounting.";
}
return self.configState[kBannedUSBBlockMessage];
}
- (NSString *)remountUSBBlockMessage {
if (!self.configState[kRemountUSBBlockMessage]) {
return @"The following device has been remounted with reduced permissions.";
}
return self.configState[kRemountUSBBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configState[kModeNotificationMonitor];
}
@@ -595,35 +696,72 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
- (SNTEventLogType)eventLogType {
NSString *s = [self.configState[kEventLogType] lowercaseString];
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
NSString *logType = [self.configState[kEventLogType] lowercaseString];
if ([logType isEqualToString:@"protobuf"]) {
return SNTEventLogTypeProtobuf;
} else if ([logType isEqualToString:@"syslog"]) {
return SNTEventLogTypeSyslog;
} else if ([logType isEqualToString:@"null"]) {
return SNTEventLogTypeNull;
} else if ([logType isEqualToString:@"file"]) {
return SNTEventLogTypeFilelog;
} else {
return SNTEventLogTypeFilelog;
}
}
- (NSString *)eventLogPath {
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
}
- (NSString *)mailDirectory {
return self.configState[kMailDirectory] ?: @"/var/db/santa/mail";
}
- (NSUInteger)mailDirectoryFileSizeThresholdKB {
return self.configState[kMailDirectoryFileSizeThresholdKB]
? [self.configState[kMailDirectoryFileSizeThresholdKB] unsignedIntegerValue]
: 100;
}
- (NSUInteger)mailDirectorySizeThresholdMB {
return self.configState[kMailDirectorySizeThresholdMB]
? [self.configState[kMailDirectorySizeThresholdMB] unsignedIntegerValue]
: 500;
}
- (float)mailDirMaxFlushTime {
return self.configState[kMailDirectoryEventMaxFlushTimeSec]
? [self.configState[kMailDirectoryEventMaxFlushTimeSec] floatValue]
: 5.0;
}
- (BOOL)enableMachineIDDecoration {
NSNumber *number = self.configState[kEnableMachineIDDecoration];
return number ? [number boolValue] : NO;
}
- (BOOL)enableSystemExtension {
if (@available(macOS 10.15, *)) {
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:@"/Library/Extensions/santa-driver.kext"]) return YES;
NSNumber *number = self.configState[kEnableSystemExtension];
return number ? [number boolValue] : YES;
} else {
return NO;
}
}
- (BOOL)enableSysxCache {
NSNumber *number = self.configState[kEnableSysxCache];
return number ? [number boolValue] : YES;
}
- (BOOL)enableCleanSyncEventUpload {
NSNumber *number = self.configState[kSyncEnableCleanSyncEventUpload];
return number ? [number boolValue] : NO;
}
- (BOOL)enableAllEventUpload {
NSNumber *n = self.syncState[kEnableAllEventUploadKey];
if (n) return [n boolValue];
return [self.configState[kEnableAllEventUploadKey] boolValue];
}
- (void)setEnableAllEventUpload:(BOOL)enabled {
[self updateSyncStateForKey:kEnableAllEventUploadKey value:@(enabled)];
}
- (BOOL)enableForkAndExitLogging {
NSNumber *number = self.configState[kEnableForkAndExitLogging];
return number ? [number boolValue] : NO;
@@ -660,6 +798,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
}
- (void)setBlockUSBMount:(BOOL)enabled {
[self updateSyncStateForKey:kBlockUSBMountKey value:@(enabled)];
}
- (BOOL)blockUSBMount {
NSNumber *number = self.configState[kBlockUSBMountKey];
return number ? [number boolValue] : NO;
}
///
/// Returns YES if all of the necessary options are set to export metrics, NO
/// otherwise.
@@ -671,12 +818,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (SNTMetricFormatType)metricFormat {
NSString *normalized = [self.configState[kMetricFormat] lowercaseString];
normalized = [normalized stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([normalized isEqualToString:@"rawjson"]) {
return SNTMetricFormatTypeRawJSON;
} else if ([normalized isEqualToString:@"json"]) {
return SNTMetricFormatTypeJSON;
} else if ([normalized isEqualToString:@"monarchjson"]) {
return SNTMetricFormatTypeMonarchJSON;
} else {
return SNTMetricFormatTypeUnknown;
}
@@ -686,6 +834,30 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [NSURL URLWithString:self.configState[kMetricURL]];
}
// Returns a default value of 30 (for 30 seconds).
- (NSUInteger)metricExportInterval {
NSNumber *configuredInterval = self.configState[kMetricExportInterval];
if (configuredInterval == nil) {
return 30;
}
return [configuredInterval unsignedIntegerValue];
}
// Returns a default value of 30 (for 30 seconds).
- (NSUInteger)metricExportTimeout {
NSNumber *configuredInterval = self.configState[kMetricExportTimeout];
if (configuredInterval == nil) {
return 30;
}
return [configuredInterval unsignedIntegerValue];
}
- (NSDictionary *)extraMetricLabels {
return self.configState[kMetricExtraLabels];
}
#pragma mark Private
///

View File

@@ -0,0 +1,27 @@
/// Copyright 2022 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/Foundation.h>
@interface SNTDeviceEvent : NSObject <NSSecureCoding>
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname;
@property NSString *mntonname;
@property NSString *mntfromname;
@property NSArray<NSString *> *remountArgs;
- (NSString *)readableRemountArgs;
@end

View File

@@ -0,0 +1,63 @@
#import "Source/common/SNTDeviceEvent.h"
@implementation SNTDeviceEvent
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
#define ENCODE(obj, key) \
if (obj) [coder encodeObject:obj forKey:key]
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
#define DECODEARRAY(cls, key) \
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
forKey:key]
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname {
self = [super init];
if (self) {
_mntonname = mntonname;
_mntfromname = mntfromname;
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.mntonname, @"mntonname");
ENCODE(self.mntfromname, @"mntfromname");
ENCODE(self.remountArgs, @"remountArgs");
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_mntonname = DECODE(NSString, @"mntonname");
_mntfromname = DECODE(NSString, @"mntfromname");
_remountArgs = DECODEARRAY(NSString, @"remountArgs");
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTDeviceEvent '%@' -> '%@' (with permissions: [%@]",
self.mntfromname, self.mntonname,
[self.remountArgs componentsJoinedByString:@", "]];
}
- (NSString *)readableRemountArgs {
NSMutableArray<NSString *> *readable = [NSMutableArray array];
for (NSString *arg in self.remountArgs) {
if ([arg isEqualToString:@"rdonly"]) {
[readable addObject:@"read-only"];
} else if ([arg isEqualToString:@"noexec"]) {
[readable addObject:@"block executables"];
} else {
[readable addObject:arg];
}
}
return [readable componentsJoinedByString:@", "];
}
@end

View File

@@ -13,27 +13,12 @@
/// limitations under the License.
///
/// Logging definitions, for both kernel and user space.
/// Logging definitions
///
#ifndef SANTA__COMMON__LOGGING_H
#define SANTA__COMMON__LOGGING_H
#ifdef KERNEL
#include <IOKit/IOLib.h>
#ifdef DEBUG
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
#else // DEBUG
#define LOGD(format, ...)
#endif // DEBUG
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
#else // KERNEL
#ifdef __cplusplus
extern "C" {
#endif
@@ -64,10 +49,11 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
/// Get the logging level for this process.
LogLevel EffectiveLogLevel();
#ifdef __cplusplus
} // extern C
#endif
#endif // KERNEL
#endif // SANTA__COMMON__LOGGING_H

View File

@@ -19,12 +19,6 @@
#import <asl.h>
#import <pthread.h>
#ifdef DEBUG
static LogLevel logLevel = LOG_LEVEL_DEBUG;
#else
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
#endif
void syslogClientDestructor(void *arg) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -32,6 +26,21 @@ void syslogClientDestructor(void *arg) {
#pragma clang diagnostic pop
}
LogLevel EffectiveLogLevel() {
#ifdef DEBUG
static LogLevel logLevel = LOG_LEVEL_DEBUG;
#else
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
#endif
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([SNTConfigurator configurator].enableDebugLogging) {
logLevel = LOG_LEVEL_DEBUG;
}
});
return logLevel;
}
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
static BOOL useSyslog = NO;
static NSString *binaryName;
@@ -41,10 +50,6 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
dispatch_once(&pred, ^{
binaryName = [[NSProcessInfo processInfo] processName];
if ([SNTConfigurator configurator].enableDebugLogging) {
logLevel = LOG_LEVEL_DEBUG;
}
// If requested, redirect output to syslog.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
@@ -53,7 +58,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
}
});
if (logLevel < level) return;
if (EffectiveLogLevel() < level) return;
va_list args;
va_start(args, format);

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import <Foundation/Foundation.h>
#import "SNTCommonEnums.h"
/**
* Provides an abstraction for various metric systems that will be exported to
@@ -48,6 +49,8 @@ typedef NS_ENUM(NSInteger, SNTMetricType) {
SNTMetricTypeCounter = 9,
};
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType);
@interface SNTMetric : NSObject
- (NSDictionary *)export;
@end
@@ -100,8 +103,21 @@ typedef NS_ENUM(NSInteger, SNTMetricType) {
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)text;
/**
* Returns a shared global instance with default root labels and metrics registered.
*/
+ (instancetype)sharedInstance;
/**
* Add a root label to the MetricSet.
*/
- (void)addRootLabel:(NSString *)label value:(NSString *)value;
/**
* Remove a root label from the MetricSet.
*/
- (void)removeRootLabel:(NSString *)labelName;
/**
* Returns a int64 gauge metric with the given Streamz name and help text,
* registered with this MetricSet.
@@ -170,4 +186,12 @@ typedef NS_ENUM(NSInteger, SNTMetricType) {
- (NSDictionary *)export;
@end
// Returns a human readble string from an SNTMetricFormat type
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format);
/** Normalizes dates in an exported dictionary to be ISO8601 timestamp strings in
* UTC time.
*/
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics);
NS_ASSUME_NONNULL_END

View File

@@ -13,6 +13,24 @@
/// limitations under the License.
#import "SNTMetricSet.h"
#import "SNTCommonEnums.h"
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
NSString *typeStr;
switch (metricType) {
case SNTMetricTypeConstantBool: typeStr = @"SNTMetricTypeConstantBool"; break;
case SNTMetricTypeConstantString: typeStr = @"SNTMetricTypeConstantString"; break;
case SNTMetricTypeConstantInt64: typeStr = @"SNTMetricTypeConstantInt64"; break;
case SNTMetricTypeConstantDouble: typeStr = @"SNTMetricTypeConstantDouble"; break;
case SNTMetricTypeGaugeBool: typeStr = @"SNTMetricTypeGaugeBool"; break;
case SNTMetricTypeGaugeString: typeStr = @"SNTMetricTypeGaugeString"; break;
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
default: typeStr = [NSString stringWithFormat:@"SNTMetricTypeUnknown %ld", metricType]; break;
}
return typeStr;
}
/**
* SNTMetricValue encapsulates the value of a metric along with the creation
@@ -257,6 +275,7 @@
NSMutableDictionary *metricDict = [NSMutableDictionary dictionaryWithCapacity:_fieldNames.count];
metricDict[@"type"] = [NSNumber numberWithInt:(int)_type];
metricDict[@"fields"] = [[NSMutableDictionary alloc] init];
metricDict[@"description"] = [_help copy];
if (_fieldNames.count == 0) {
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
@@ -431,6 +450,17 @@
NSMutableArray<void (^)(void)> *_callbacks;
}
+ (instancetype)sharedInstance {
static SNTMetricSet *sharedMetrics;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMetrics = [[SNTMetricSet alloc] init];
});
return sharedMetrics;
}
- (instancetype)init {
self = [super init];
if (self) {
@@ -461,6 +491,12 @@
}
}
- (void)removeRootLabel:(NSString *)label {
@synchronized(self) {
[_rootLabels removeObjectForKey:label];
}
}
- (SNTMetric *)registerMetric:(nonnull SNTMetric *)metric {
@synchronized(self) {
SNTMetric *oldMetric = _metrics[[metric name]];
@@ -485,8 +521,7 @@
SNTMetricCounter *c = [[SNTMetricCounter alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:c];
return c;
return (SNTMetricCounter *)[self registerMetric:c];
}
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
@@ -495,8 +530,7 @@
SNTMetricInt64Gauge *g = [[SNTMetricInt64Gauge alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:g];
return g;
return (SNTMetricInt64Gauge *)[self registerMetric:g];
}
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
@@ -506,8 +540,7 @@
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:g];
return g;
return (SNTMetricDoubleGauge *)[self registerMetric:g];
}
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
@@ -517,8 +550,7 @@
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:s];
return s;
return (SNTMetricStringGauge *)[self registerMetric:s];
}
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
@@ -528,8 +560,7 @@
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:b];
return b;
return (SNTMetricBooleanGauge *)[self registerMetric:b];
}
- (void)addConstantStringWithName:(NSString *)name
@@ -581,19 +612,61 @@
}
@synchronized(self) {
// Walk root labels
NSMutableDictionary *exportDict = [[NSMutableDictionary alloc] init];
exportDict[@"root_labels"] = [NSDictionary dictionaryWithDictionary:_rootLabels];
exportDict[@"root_labels"] = [_rootLabels copy];
exportDict[@"metrics"] = [[NSMutableDictionary alloc] init];
// sort the metrics so we always get the same output.
for (id metricName in _metrics) {
SNTMetric *metric = [_metrics objectForKey:metricName];
exportDict[@"metrics"][metricName] = [metric export];
// TODO(markowsky) Sort the metrics so we always get the same output.
for (NSString *metricName in _metrics) {
exportDict[@"metrics"][metricName] = [_metrics[metricName] export];
}
exported = [NSDictionary dictionaryWithDictionary:exportDict];
}
return exported;
}
// Returns a human readble string from an SNTMetricFormat type
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format) {
switch (format) {
case SNTMetricFormatTypeRawJSON: return @"rawjson";
case SNTMetricFormatTypeMonarchJSON: return @"monarchjson";
default: return @"Unknown Metric Format";
}
}
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics) {
NSMutableDictionary *mutableMetrics = [metrics mutableCopy];
id formatter;
if (@available(macOS 10.13, *)) {
NSISO8601DateFormatter *isoFormatter = [[NSISO8601DateFormatter alloc] init];
isoFormatter.formatOptions =
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
formatter = isoFormatter;
} else {
NSDateFormatter *localFormatter = [[NSDateFormatter alloc] init];
[localFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[localFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
formatter = localFormatter;
}
for (NSString *metricName in mutableMetrics[@"metrics"]) {
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];
for (NSString *field in metric[@"fields"]) {
NSMutableArray<NSMutableDictionary *> *values = metric[@"fields"][field];
[values enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
values[index][@"created"] = [formatter stringFromDate:values[index][@"created"]];
values[index][@"last_updated"] = [formatter stringFromDate:values[index][@"last_updated"]];
}];
}
}
return mutableMetrics;
}
@end

View File

@@ -20,6 +20,9 @@
@interface SNTMetricSetTest : XCTestCase
@end
@interface SNTMetricSetHelperFunctionsTest : XCTestCase
@end
// Stub out NSDate's date method
@implementation NSDate (custom)
@@ -42,10 +45,10 @@
XCTAssertNotNil(c, @"Expected returned SNTMetricCounter to not be nil");
[c incrementForFieldValues:@[ @"certificate" ]];
XCTAssertEqual(1, [c getCountForFieldValues:@[ @"certificate" ]],
@"Counter not incremendted by 1");
@"Counter not incremented by 1");
[c incrementBy:3 forFieldValues:@[ @"certificate" ]];
XCTAssertEqual(4, [c getCountForFieldValues:@[ @"certificate" ]],
@"Counter not incremendted by 3");
@"Counter not incremented by 3");
}
- (void)testExportNSDictionary {
@@ -60,6 +63,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
@"description" : @"Count of exec events broken out by rule type.",
@"fields" : @{
@"rule_type" : @[ @{
@"value" : @"certificate",
@@ -72,6 +76,19 @@
XCTAssertEqualObjects([c export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricCounter *a = [metricSet counterWithName:@"/santa/counter"
fieldNames:@[]
helpText:@"Test counter."];
SNTMetricCounter *b = [metricSet counterWithName:@"/santa/counter"
fieldNames:@[]
helpText:@"Test counter."];
XCTAssertEqual(a, b, @"Unexpected new counter returned.");
}
@end
@implementation SNTMetricBooleanGaugeTest
@@ -96,6 +113,7 @@
[b set:true forFieldValues:@[]];
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeBool],
@"description" : @"Is the daemon connected.",
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@@ -109,6 +127,20 @@
NSDictionary *output = [b export];
XCTAssertEqualObjects(output, expected);
}
- (void)testAddingBooleanWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricBooleanGauge *a = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
fieldNames:@[]
helpText:@"Is the daemon connected."];
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
fieldNames:@[]
helpText:@"Is the daemon connected."];
XCTAssertEqual(a, b, @"Unexpected new boolean gauge returned.");
}
@end
@implementation SNTMetricGaugeInt64Test
@@ -150,6 +182,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"description" : @"Count of rules broken out by rule type.",
@"fields" : @{
@"rule_type" : @[ @{
@"value" : @"binary",
@@ -162,6 +195,20 @@
XCTAssertEqualObjects([g export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricInt64Gauge *a = [metricSet int64GaugeWithName:@"/santa/int64gauge"
fieldNames:@[]
helpText:@"Test gauge."];
SNTMetricInt64Gauge *b = [metricSet int64GaugeWithName:@"/santa/int64gauge"
fieldNames:@[]
helpText:@"Test gauge."];
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
}
@end
@implementation SNTMetricDoubleGaugeTest
@@ -201,6 +248,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeDouble],
@"description" : @"CPU time consumed by this process.",
@"fields" : @{
@"mode" : @[
@{
@@ -220,6 +268,19 @@
};
XCTAssertEqualObjects([g export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricDoubleGauge *a = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
fieldNames:@[]
helpText:@"Test gauge."];
SNTMetricDoubleGauge *b = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
fieldNames:@[]
helpText:@"Test gauge."];
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
}
@end
@implementation SNTMetricStringGaugeTest
@@ -233,6 +294,7 @@
[s set:@"testValue" forFieldValues:@[]];
XCTAssertEqualObjects([s getStringValueForFieldValues:@[]], @"testValue");
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
@@ -244,6 +306,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeString],
@"description" : @"String description of the mode.",
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@@ -256,6 +319,20 @@
XCTAssertEqualObjects([s export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricStringGauge *a = [metricSet stringGaugeWithName:@"/santa/stringgauge"
fieldNames:@[]
helpText:@"Test gauge."];
SNTMetricStringGauge *b = [metricSet stringGaugeWithName:@"/santa/stringgauge"
fieldNames:@[]
helpText:@"Test gauge."];
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
}
@end
@implementation SNTMetricSetTest
@@ -265,8 +342,18 @@
NSDictionary *expected = @{@"root_labels" : @{@"hostname" : @"localhost"}, @"metrics" : @{}};
NSDictionary *output = [metricSet export];
XCTAssertEqualObjects(output, expected);
XCTAssertEqualObjects(expected, [metricSet export]);
// ensure that adding a rootLabel with the same name overwrites.
expected = @{@"root_labels" : @{@"hostname" : @"localhost2"}, @"metrics" : @{}};
[metricSet addRootLabel:@"hostname" value:@"localhost2"];
XCTAssertEqualObjects(expected, [metricSet export],
@"failed to overwrite rootLabel with second call to addRootLabel");
// ensure that removing a rootLabelWorks
expected = @{@"root_labels" : @{}, @"metrics" : @{}};
[metricSet removeRootLabel:@"hostname"];
}
- (void)testDoubleRegisteringIncompatibleMetricsFails {
@@ -313,6 +400,7 @@
NSDictionary *expected = @{
@"/tautology" : @{
@"description" : @"The first rule of tautology club is the first rule",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
@"fields" : @{
@"" : @[ @{
@@ -337,6 +425,7 @@
NSDictionary *expected = @{
@"/build/label" : @{
@"description" : @"Build label for the binary",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
@"fields" : @{
@"" : @[ @{
@@ -360,6 +449,7 @@
NSDictionary *expected = @{
@"/deep/thought/answer" : @{
@"description" : @"Life, the universe, and everything",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
@"fields" : @{
@"" : @[ @{
@@ -386,10 +476,10 @@
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
helpText:@"Is santad using the endpoint security framework."
value:TRUE];
[metricSet addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this LogDumper instance, in microseconds "
@"since epoch"
value:(long long)(0x12345668910)];
[metricSet
addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this santad instance, in microseconds since epoch"
value:(long long)(0x12345668910)];
// Add Metrics
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"rule_type" ]
@@ -414,7 +504,7 @@
SNTMetricInt64Gauge *residentMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
fieldNames:@[]
helpText:@"The resident set siz of this process."];
helpText:@"The resident set size of this process."];
[metricSet registerCallback:^(void) {
[virtualMemoryGauge set:987654321 forFieldValues:@[]];
@@ -425,6 +515,7 @@
@"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"},
@"metrics" : @{
@"/build/label" : @{
@"description" : @"Software version running.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
@"fields" : @{
@"" : @[ @{
@@ -436,6 +527,7 @@
}
},
@"/santa/events" : @{
@"description" : @"Count of events on the host",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
@"fields" : @{
@"rule_type" : @[
@@ -455,6 +547,7 @@
},
},
@"/santa/rules" : @{
@"description" : @"Number of rules.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"rule_type" : @[
@@ -474,6 +567,7 @@
},
},
@"/santa/using_endpoint_security_framework" : @{
@"description" : @"Is santad using the endpoint security framework.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
@"fields" : @{
@"" : @[ @{
@@ -485,6 +579,7 @@
}
},
@"/proc/birth_timestamp" : @{
@"description" : @"Start time of this santad instance, in microseconds since epoch",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
@"fields" : @{
@"" : @[ @{
@@ -496,6 +591,7 @@
},
},
@"/proc/memory/virtual_size" : @{
@"description" : @"The virtual memory size of this process.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@@ -507,6 +603,7 @@
}
},
@"/proc/memory/resident_size" : @{
@"description" : @"The resident set size of this process.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@@ -522,5 +619,57 @@
XCTAssertEqualObjects([metricSet export], expected);
}
@end
@implementation SNTMetricSetHelperFunctionsTest
- (void)testMakeMetricString {
NSArray<NSDictionary *> *tests = @[
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeUnknown],
@"expected" : @"SNTMetricTypeUnknown 0"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
@"expected" : @"SNTMetricTypeConstantBool"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
@"expected" : @"SNTMetricTypeConstantString"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
@"expected" : @"SNTMetricTypeConstantInt64"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
@"expected" : @"SNTMetricTypeConstantDouble"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
@"expected" : @"SNTMetricTypeGaugeBool"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
@"expected" : @"SNTMetricTypeGaugeString"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
@"expected" : @"SNTMetricTypeGaugeInt64"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
@"expected" : @"SNTMetricTypeGaugeDouble"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
@"expected" : @"SNTMetricTypeCounter"
}
];
for (NSDictionary *test in tests) {
NSString *output = SNTMetricMakeStringFromMetricType([test[@"input"] integerValue]);
XCTAssertEqualObjects(test[@"expected"], output, @"expected %@ got %@", test[@"expected"],
output);
}
}
@end

View File

@@ -14,13 +14,6 @@
#include "Source/common/SNTPrefixTree.h"
#ifdef KERNEL
#include <libkern/locks.h>
#include "Source/common/SNTLogging.h"
#else
#include <string.h>
#include <mutex>
@@ -46,25 +39,14 @@
#define lck_mtx_lock(l) l->lock()
#define lck_mtx_unlock(l) l->unlock()
#endif // KERNEL
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
root_ = new SantaPrefixNode();
node_count_ = 0;
max_nodes_ = max_nodes;
#ifdef KERNEL
spt_lock_grp_attr_ = lck_grp_attr_alloc_init();
spt_lock_grp_ =
lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
spt_lock_attr_ = lck_attr_alloc_init();
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
spt_add_lock_ = lck_mtx_alloc_init(spt_lock_grp_, spt_lock_attr_);
#else
pthread_rwlock_init(&spt_lock_, nullptr);
spt_add_lock_ = new std::mutex;
#endif
}
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
@@ -241,32 +223,5 @@ SNTPrefixTree::~SNTPrefixTree() {
root_ = nullptr;
lck_rw_unlock_exclusive(spt_lock_);
#ifdef KERNEL
if (spt_lock_) {
lck_rw_free(spt_lock_, spt_lock_grp_);
spt_lock_ = nullptr;
}
if (spt_add_lock_) {
lck_mtx_free(spt_add_lock_, spt_lock_grp_);
spt_add_lock_ = nullptr;
}
if (spt_lock_attr_) {
lck_attr_free(spt_lock_attr_);
spt_lock_attr_ = nullptr;
}
if (spt_lock_grp_) {
lck_grp_free(spt_lock_grp_);
spt_lock_grp_ = nullptr;
}
if (spt_lock_grp_attr_) {
lck_grp_attr_free(spt_lock_grp_attr_);
spt_lock_grp_attr_ = nullptr;
}
#else
pthread_rwlock_destroy(&spt_lock_);
#endif
}

View File

@@ -18,15 +18,11 @@
#include <IOKit/IOReturn.h>
#include <sys/param.h>
#ifdef KERNEL
#include <libkern/locks.h>
#else
// Support for unit testing.
#include <pthread.h>
#include <stdint.h>
#include <mutex>
#endif // KERNEL
///
/// SantaPrefixTree is a simple prefix tree implementation.
@@ -88,16 +84,8 @@ class SNTPrefixTree {
uint32_t max_nodes_;
uint32_t node_count_;
#ifdef KERNEL
lck_grp_t *spt_lock_grp_;
lck_grp_attr_t *spt_lock_grp_attr_;
lck_attr_t *spt_lock_attr_;
lck_rw_t *spt_lock_;
lck_mtx_t *spt_add_lock_;
#else // KERNEL
pthread_rwlock_t spt_lock_;
std::mutex *spt_add_lock_;
#endif // KERNEL
};
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */

View File

@@ -45,26 +45,29 @@
[UUIDs addObject:[NSUUID UUID].UUIDString];
}
__block BOOL stop = NO;
// Create a bunch of background noise.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_apply(UINT64_MAX, dispatch_get_global_queue(0, 0), ^(size_t i) {
t->HasPrefix([UUIDs[i % count] UTF8String]);
});
for (uint64_t i = 0; i < UINT64_MAX; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
t->HasPrefix([UUIDs[i % count] UTF8String]);
});
if (stop) return;
}
});
// Fill up the tree.
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
if (t->AddPrefix([UUIDs[i] UTF8String]) != kIOReturnSuccess) {
XCTFail();
}
XCTAssertEqual(t->AddPrefix([UUIDs[i] UTF8String]), kIOReturnSuccess);
});
// Make sure every leaf byte is found.
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
if (!t->HasPrefix([UUIDs[i] UTF8String])) {
XCTFail();
}
XCTAssertTrue(t->HasPrefix([UUIDs[i] UTF8String]));
});
stop = YES;
}
@end

View File

@@ -24,7 +24,7 @@
///
/// The hash of the object this rule is for
///
@property(copy) NSString *shasum;
@property(copy) NSString *identifier;
///
/// The state of this rule
@@ -50,7 +50,7 @@
///
/// Designated initializer.
///
- (instancetype)initWithShasum:(NSString *)shasum
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
@@ -59,7 +59,7 @@
///
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
///
- (instancetype)initWithShasum:(NSString *)shasum
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;

View File

@@ -20,14 +20,14 @@
@implementation SNTRule
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
self = [super init];
if (self) {
_shasum = shasum;
_identifier = identifier;
_state = state;
_type = type;
_customMsg = customMsg;
@@ -36,11 +36,11 @@
return self;
}
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithShasum:shasum state:state type:type customMsg:customMsg timestamp:0];
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithIdentifier:identifier state:state type:type customMsg:customMsg timestamp:0];
// Initialize timestamp to current time if rule is transitive.
if (self && state == SNTRuleStateAllowTransitive) {
[self resetTimestamp];
@@ -61,7 +61,7 @@
}
- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.shasum, @"shasum");
ENCODE(self.identifier, @"identifier");
ENCODE(@(self.state), @"state");
ENCODE(@(self.type), @"type");
ENCODE(self.customMsg, @"custommsg");
@@ -71,7 +71,7 @@
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_shasum = DECODE(NSString, @"shasum");
_identifier = DECODE(NSString, @"identifier");
_state = [DECODE(NSNumber, @"state") intValue];
_type = [DECODE(NSNumber, @"type") intValue];
_customMsg = DECODE(NSString, @"custommsg");
@@ -88,22 +88,22 @@
if (other == self) return YES;
if (![other isKindOfClass:[SNTRule class]]) return NO;
SNTRule *o = other;
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
return ([self.identifier isEqual:o.identifier] && self.state == o.state && self.type == o.type);
}
- (NSUInteger)hash {
NSUInteger prime = 31;
NSUInteger result = 1;
result = prime * result + [self.shasum hash];
result = prime * result + [self.identifier hash];
result = prime * result + self.state;
result = prime * result + self.type;
return result;
}
- (NSString *)description {
return
[NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
return [NSString
stringWithFormat:@"SNTRule: Identifier: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.identifier, self.state, self.type, (unsigned long)self.timestamp];
}
#pragma mark Last-access Timestamp

View File

@@ -49,4 +49,9 @@
///
+ (NSString *)longHostname;
///
/// @return Model Identifier
///
+ (NSString *)modelIdentifier;
@end

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "Source/common/SNTSystemInfo.h"
#include <sys/sysctl.h>
@implementation SNTSystemInfo
@@ -60,6 +61,13 @@
return @(hostname);
}
+ (NSString *)modelIdentifier {
char model[32];
size_t len = 32;
sysctlbyname("hw.model", model, &len, NULL, 0);
return @(model);
}
#pragma mark - Internal
+ (NSDictionary *)_systemVersionDictionary {

View File

@@ -34,6 +34,7 @@
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply;
///
@@ -46,13 +47,15 @@
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
- (void)setAllowedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply;
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setEnableAllEventUpload:(BOOL)enabled reply:(void (^)(void))reply;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
@end

View File

@@ -27,13 +27,10 @@ NSString *const kBundleID = @"com.google.santa.daemon";
@implementation SNTXPCControlInterface
+ (NSString *)serviceID {
if ([[SNTConfigurator configurator] enableSystemExtension]) {
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
// "teamid.com.google.santa.daemon.xpc"
NSString *t = cs.signingInformation[@"teamid"];
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
}
return kBundleID;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
// "teamid.com.google.santa.daemon.xpc"
NSString *t = cs.signingInformation[@"teamid"];
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
}
+ (NSString *)systemExtensionID {

View File

@@ -14,13 +14,13 @@
#import "Source/common/SNTXPCMetricServiceInterface.h"
@implementation SNTXPCMetricServiceInterface
+ (NSXPCInterface *)metricServiceInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTMetricServiceXPC)];
[r setClasses:[NSSet setWithObjects:[NSDictionary class], nil]
[r setClasses:[NSSet setWithObjects:[NSDictionary class], [NSArray class], [NSNumber class],
[NSString class], [NSDate class], nil]
forSelector:@selector(exportForMonitoring:)
argumentIndex:0
ofReply:NO];

View File

@@ -18,10 +18,12 @@
#import "Source/common/SNTXPCBundleServiceInterface.h"
@class SNTStoredEvent;
@class SNTDeviceEvent;
/// Protocol implemented by SantaGUI and utilized by santad
@protocol SNTNotifierXPC
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message;
- (void)postClientModeNotification:(SNTClientMode)clientmode;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
- (void)updateCountsForEvent:(SNTStoredEvent *)event

View File

@@ -20,18 +20,37 @@
@class SNTStoredEvent;
/// A block that reports the number of rules processed.
/// TODO(bur): Add more details about the sync.
typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
///
/// Protocol implemented by syncservice and utilized by daemon and ctl for communication with a
/// sync server.
///
@protocol SNTSyncServiceXPC
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply;
- (void)isFCMListening:(void (^)(BOOL))reply;
- (void)performFullSyncWithReply:(SNTFullSyncReplyBlock)reply;
// The syncservice regularly syncs with a configured sync server. Use this method to sync out of
// band. The syncservice ensures syncs do not run concurrently.
//
// Pass an NSXPCListenerEndpoint whose associated NSXPCListener exports an object that implements
// the SNTSyncServiceLogReceiverXPC protocol. The caller will receive sync logs over this listener.
// This is required.
//
// Syncs are enqueued in order and executed serially. kMaxEnqueuedSyncs limits the number of syncs
// in the queue. If the queue is full calls to this method will be dropped and
// SNTSyncStatusTypeTooManySyncsInProgress will be passed into the reply block.
//
// Pass true to isClean to perform a clean sync, defaults to false.
//
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
isClean:(BOOL)cleanSync
reply:(void (^)(SNTSyncStatusType))reply;
// Spindown the syncservice. The syncservice will not automatically start back up.
// A new connection to the syncservice will bring it back up. This allows us to avoid running
// the syncservice needlessly when there is no configured sync server.
- (void)spindown;
@end
@interface SNTXPCSyncServiceInterface : NSObject
@@ -54,3 +73,11 @@ typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
+ (MOLXPCConnection *)configuredConnection;
@end
///
/// Protocol implemented by santactl sync and used to receive log messages from
/// the syncservice during a user initiated sync.
///
@protocol SNTSyncServiceLogReceiverXPC
- (void)didReceiveLog:(NSString *)log;
@end

View File

@@ -1,37 +0,0 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import "Source/common/SNTCommonEnums.h"
@class SNTStoredEvent;
/// Protocol implemented by santactl and utilized by santad
@protocol SNTSyncdXPC
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end
@interface SNTXPCSyncdInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)syncdInterface;
@end

View File

@@ -1,32 +0,0 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTStoredEvent.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
argumentIndex:0
ofReply:NO];
return r;
}
@end

View File

@@ -16,7 +16,7 @@
#import <MOLCertificate/MOLCertificate.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/common/SNTCommon.h"
@class SNTRule;
@class SNTStoredEvent;
@@ -28,18 +28,17 @@
@protocol SNTUnprivilegedDaemonControlXPC
///
/// Kernel ops
/// Cache Ops
///
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
///
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive))reply;
int64_t transitive, int64_t teamID))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
///
@@ -57,6 +56,7 @@
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTEventState))reply;
///
@@ -71,6 +71,11 @@
- (void)enableBundles:(void (^)(BOOL))reply;
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
///
/// Metrics ops
///
- (void)metrics:(void (^)(NSDictionary *))reply;
///
/// GUI Ops
///

View File

@@ -20,28 +20,19 @@
#include <stdint.h>
#include <sys/cdefs.h>
#include "Source/common/SNTKernelCommon.h"
#ifdef KERNEL
#include <IOKit/IOLib.h>
#else // KERNEL
// Support for unit testing.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "Source/common/SNTCommon.h"
#define panic(args...) \
printf(args); \
printf("\n"); \
abort()
#define IOMallocAligned(sz, alignment) malloc(sz);
#define IOFreeAligned(addr, sz) free(addr)
#define OSTestAndSet OSAtomicTestAndSet
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif // KERNEL
/**
A type to specialize to help SantaCache with its hashing.
@@ -88,8 +79,7 @@ class SantaCache {
(1 << (32 -
__builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
if (unlikely(bucket_count_ > UINT32_MAX)) bucket_count_ = UINT32_MAX;
buckets_ = (struct bucket *)IOMallocAligned(
bucket_count_ * sizeof(struct bucket), 2);
buckets_ = (struct bucket *)malloc(bucket_count_ * sizeof(struct bucket));
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
}
@@ -98,7 +88,7 @@ class SantaCache {
*/
~SantaCache() {
clear();
IOFreeAligned(buckets_, bucket_count_ * sizeof(struct bucket));
free(buckets_);
}
/**
@@ -173,7 +163,7 @@ class SantaCache {
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
struct entry *next_entry = entry->next;
IOFreeAligned(entry, sizeof(struct entry));
free(entry);
entry = next_entry;
}
}
@@ -284,8 +274,8 @@ class SantaCache {
} else {
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
}
IOFreeAligned(entry, sizeof(struct entry));
OSDecrementAtomic(&count_);
free(entry);
OSAtomicDecrement64((volatile int64_t *)&count_);
}
unlock(bucket);
@@ -318,14 +308,13 @@ class SantaCache {
// Allocate a new entry, set the key and value, then put this new entry at
// the head of this bucket's linked list.
struct entry *new_entry =
(struct entry *)IOMallocAligned(sizeof(struct entry), 2);
struct entry *new_entry = (struct entry *)malloc(sizeof(struct entry));
bzero(new_entry, sizeof(struct entry));
new_entry->key = key;
new_entry->value = value;
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
OSIncrementAtomic(&count_);
OSAtomicIncrement64((volatile int64_t *)&count_);
unlock(bucket);
return true;
@@ -335,7 +324,7 @@ class SantaCache {
Lock a bucket. Spins until the lock is acquired.
*/
inline void lock(struct bucket *bucket) const {
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head))
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head))
;
}
@@ -343,7 +332,8 @@ class SantaCache {
Unlock a bucket. Panics if the lock wasn't locked.
*/
inline void unlock(struct bucket *bucket) const {
if (unlikely(OSTestAndClear(7, (volatile uint8_t *)&bucket->head))) {
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
0)) {
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
}
}
@@ -375,8 +365,6 @@ class SantaCache {
}
};
#ifndef KERNEL
#pragma clang diagnostic pop
#endif
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H

145
Source/common/santa.proto Normal file
View File

@@ -0,0 +1,145 @@
//
// !!! WARNING !!!
// This proto is in beta format and subject to change.
//
syntax = "proto3";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
option objc_class_prefix = "SNTPB";
package santa;
message ProcessInfo {
optional int32 pid = 1;
optional int32 pidversion = 2;
optional int32 ppid = 3;
optional int32 uid = 4;
optional string user = 5;
optional int32 gid = 6;
optional string group = 7;
}
message FileModification {
enum Action {
ACTION_UNKNOWN = 0;
ACTION_DELETE = 1;
ACTION_EXCHANGE = 2;
ACTION_LINK = 3;
ACTION_RENAME = 4;
ACTION_WRITE = 5;
}
optional Action action = 1;
optional string path = 2;
optional string newpath = 3;
optional string process = 4;
optional string process_path = 5;
optional ProcessInfo process_info = 6;
optional string machine_id = 7;
}
message Execution {
enum Decision {
DECISION_UNKNOWN = 0;
DECISION_ALLOW = 1;
DECISION_DENY = 2;
}
enum Reason {
REASON_UNKNOWN = 0;
REASON_BINARY = 1;
REASON_CERT = 2;
REASON_COMPILER = 3;
REASON_NOT_RUNNING = 4;
REASON_PENDING_TRANSITIVE = 5;
REASON_SCOPE = 6;
REASON_TEAM_ID = 7;
REASON_TRANSITIVE = 8;
}
enum Mode {
MODE_UNKNOWN = 0;
MODE_LOCKDOWN = 1;
MODE_MONITOR = 2;
}
optional Decision decision = 1;
optional Reason reason = 2;
optional string explain = 3;
optional string sha256 = 4;
optional string cert_sha256 = 5;
optional string cert_cn = 6;
optional string quarantine_url = 7;
optional ProcessInfo process_info = 8;
optional Mode mode = 9;
optional string path = 10;
optional string original_path = 11;
repeated string args = 12;
optional string machine_id = 13;
}
message DiskAppeared {
optional string mount = 1;
optional string volume = 2;
optional string bsd_name = 3;
optional string fs = 4;
optional string model = 5;
optional string serial = 6;
optional string bus = 7;
optional string dmg_path = 8;
optional string appearance = 9;
}
message DiskDisappeared {
optional string mount = 1;
optional string volume = 2;
optional string bsd_name = 3;
}
message Bundle {
// This is the hash of the file within the bundle that triggered the event
optional string sha256 = 1;
// This is the hash of the hashes of all executables in the bundle
optional string bundle_hash = 2;
optional string bundle_name = 3;
optional string bundle_id = 4;
optional string bundle_path = 5;
optional string path = 6;
}
message Fork {
optional ProcessInfo process_info = 1;
}
message Exit {
optional ProcessInfo process_info = 1;
}
message Allowlist {
optional int32 pid = 1;
optional int32 pidversion = 2;
optional string path = 3;
optional string sha256 = 4;
}
message SantaMessage {
google.protobuf.Timestamp event_time = 1;
oneof message {
FileModification file_modification = 2;
Execution execution = 3;
DiskAppeared disk_appeared = 4;
DiskDisappeared disk_disappeared = 5;
Bundle bundle = 6;
Fork fork = 7;
Exit exit = 8;
Allowlist allowlist = 9;
}
}
message LogBatch {
repeated google.protobuf.Any records = 1;
}

View File

@@ -2,6 +2,10 @@ load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
)
exports_files([
"Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-256.png",
])
@@ -15,6 +19,10 @@ objc_library(
"SNTAccessibleTextField.m",
"SNTAppDelegate.h",
"SNTAppDelegate.m",
"SNTBinaryMessageWindowController.h",
"SNTBinaryMessageWindowController.m",
"SNTDeviceMessageWindowController.h",
"SNTDeviceMessageWindowController.m",
"SNTMessageWindow.h",
"SNTMessageWindow.m",
"SNTMessageWindowController.h",
@@ -25,19 +33,25 @@ objc_library(
],
data = [
"Resources/AboutWindow.xib",
"Resources/DeviceMessageWindow.xib",
"Resources/MessageWindow.xib",
],
sdk_frameworks = [
"IOKit",
"SecurityInterface",
"SystemExtensions",
"UserNotifications",
],
deps = [
"//Source/common:SNTBlockMessage_SantaGUI",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCNotifierInterface",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
@@ -48,22 +62,24 @@ macos_application(
additional_contents = {
"//Source/santactl": "MacOS",
"//Source/santabundleservice": "MacOS",
"//Source/santametricservice": "MacOS",
"//Source/santasyncservice": "MacOS",
"//Source/santad:com.google.santa.daemon": "Library/SystemExtensions",
},
app_icons = glob(["Resources/Images.xcassets/**"]),
bundle_id = "com.google.santa",
bundle_name = "Santa",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
entitlements = "Santa.app.entitlements",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
entitlements = "Santa.app.entitlements",
infoplists = ["Info.plist"],
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
"//conditions:default": "//profiles:santa_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],

View File

@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
<connections>
<outlet property="aboutTextField" destination="uh6-q0-RzL" id="oGn-hV-wwc"/>
<outlet property="moreInfoButton" destination="SRu-Kf-vu5" id="Vj2-9Q-05d"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
@@ -16,7 +17,7 @@
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>
@@ -46,7 +47,7 @@ There are no user-configurable settings.</string>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SRu-Kf-vu5">
<rect key="frame" x="130" y="21" width="111" height="32"/>
<rect key="frame" x="129" y="21" width="113" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="99" id="JHv-2J-QSe"/>
</constraints>
@@ -59,7 +60,7 @@ There are no user-configurable settings.</string>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Udo-BY-n7e">
<rect key="frame" x="240" y="21" width="111" height="32"/>
<rect key="frame" x="239" y="21" width="113" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="99" id="2Xc-ax-2bV"/>
</constraints>

View File

@@ -0,0 +1,193 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTDeviceMessageWindowController">
<connections>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="515" height="318"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="515" height="318"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<userGuides>
<userLayoutGuide location="344" affinity="minX"/>
</userGuides>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="16" y="290" width="37" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</buttonCell>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="31" y="210" width="454" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="139" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device Name" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="115" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device BSD Path" id="adC-be-Beh">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Device Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="139" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device Name" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.mntonname" id="bOu-gv-1Vh"/>
</connections>
</textField>
<textField toolTip="Device BSD Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="115" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device BSD Path" id="H1b-Ui-CYo">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.mntfromname" id="Sry-KY-HDb"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="217" y="248" width="82" height="40"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="i8e-8n-tZS" userLabel="Label: Path">
<rect key="frame" x="8" y="91" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="a3r-ng-zxJ"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Remount Mode" id="4D0-8L-ihK">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Device BSD Path" verticalHuggingPriority="750" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kmz-be-oWf" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="91" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="cbM-i5-bJ9"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Remount Mode" id="qI0-Na-kxN">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.readableRemountArgs" id="bOu-gv-1Vl"/>
</connections>
</textField>
<button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="202" y="18" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Ok" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<accessibility description="Dismiss Dialog"/>
<connections>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
<constraint firstItem="kmz-be-oWf" firstAttribute="height" secondItem="i8e-8n-tZS" secondAttribute="height" id="6Ow-Sl-6Wc"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="55" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="22" id="dYg-zP-wh2"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
<constraint firstItem="kmz-be-oWf" firstAttribute="centerY" secondItem="i8e-8n-tZS" secondAttribute="centerY" id="ilC-5x-LNe"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
<constraint firstItem="i8e-8n-tZS" firstAttribute="centerY" secondItem="YNz-ka-cBi" secondAttribute="centerY" constant="24" id="uZs-XT-PuZ"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="firstBaseline" secondItem="d9e-Wv-Y5H" secondAttribute="baseline" constant="24" id="xZu-AY-wX5"/>
<constraint firstItem="i8e-8n-tZS" firstAttribute="top" secondItem="YNz-ka-cBi" secondAttribute="bottom" constant="8" symbolic="YES" id="ylw-3s-9JW"/>
</constraints>
</view>
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="261.5" y="246"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="iXx-cu-WYe"/>
</objects>
</document>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -21,13 +21,13 @@
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="540" height="479"/>
<rect key="frame" x="0.0" y="0.0" width="540" height="462"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="16" y="451" width="37" height="32"/>
<rect key="frame" x="16" y="434" width="37" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@@ -41,7 +41,7 @@
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="43" y="369" width="454" height="17"/>
<rect key="frame" x="43" y="354" width="454" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
@@ -56,7 +56,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="297" width="142" height="17"/>
<rect key="frame" x="8" y="283" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
</constraints>
@@ -70,7 +70,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="272" width="142" height="17"/>
<rect key="frame" x="8" y="259" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
</constraints>
@@ -84,7 +84,7 @@
</textFieldCell>
</textField>
<textField toolTip="Binary Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="297" width="315" height="17"/>
<rect key="frame" x="187" y="283" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
</constraints>
@@ -98,7 +98,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<rect key="frame" x="8" y="247" width="142" height="17"/>
<rect key="frame" x="8" y="235" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -109,7 +109,7 @@
</textFieldCell>
</textField>
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="247" width="315" height="17"/>
<rect key="frame" x="187" y="235" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Dem-wH-KHm"/>
</constraints>
@@ -127,7 +127,7 @@
</connections>
</textField>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="62" y="248" width="15" height="15"/>
<rect key="frame" x="62" y="235" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
@@ -149,7 +149,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
<rect key="frame" x="8" y="222" width="142" height="17"/>
<rect key="frame" x="8" y="212" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -160,7 +160,7 @@
</textFieldCell>
</textField>
<textField toolTip="SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="187" y="222" width="219" height="17"/>
<rect key="frame" x="187" y="214" width="219" height="13"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
</constraints>
@@ -177,7 +177,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MhO-U0-MLR" userLabel="Label: Bundle Identifier">
<rect key="frame" x="8" y="197" width="142" height="17"/>
<rect key="frame" x="8" y="191" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bundle Identifier" id="LEe-u0-52o">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -195,7 +195,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
<rect key="frame" x="8" y="157" width="142" height="17"/>
<rect key="frame" x="8" y="156" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -206,7 +206,7 @@
</textFieldCell>
</textField>
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="157" width="294" height="17"/>
<rect key="frame" x="187" y="156" width="294" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
@@ -229,7 +229,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<rect key="frame" x="8" y="132" width="142" height="17"/>
<rect key="frame" x="8" y="132" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -240,17 +240,16 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="154" y="34" width="112" height="23"/>
<rect key="frame" x="147" y="30" width="126" height="32"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<buttonCell key="cell" type="push" title="Open Event..." bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
@@ -258,7 +257,7 @@ DQ
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="272" width="315" height="17"/>
<rect key="frame" x="187" y="259" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
</constraints>
@@ -272,7 +271,7 @@ DQ
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="113" y="80" width="315" height="29"/>
<rect key="frame" x="110" y="80" width="319" height="29"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
@@ -285,18 +284,17 @@ DQ
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="278" y="34" width="110" height="23"/>
<rect key="frame" x="271" y="28" width="124" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Ignore" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<buttonCell key="cell" type="push" title="Ignore" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
Gw
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<accessibility description="Dismiss Dialog"/>
<connections>
@@ -305,7 +303,7 @@ DQ
</connections>
</button>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="132" width="294" height="17"/>
<rect key="frame" x="187" y="132" width="294" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
@@ -322,14 +320,14 @@ DQ
</binding>
</connections>
</textField>
<progressIndicator wantsLayer="YES" canDrawConcurrently="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
<rect key="frame" x="187" y="199" width="217" height="12"/>
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
<rect key="frame" x="187" y="193" width="217" height="12"/>
<constraints>
<constraint firstAttribute="width" constant="217" id="M22-Dv-KIP"/>
</constraints>
</progressIndicator>
<textField toolTip="Bundle SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xP7-jE-NF8">
<rect key="frame" x="187" y="197" width="219" height="17"/>
<rect key="frame" x="187" y="193" width="219" height="13"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="s7W-o9-2nN"/>
</constraints>
@@ -346,7 +344,7 @@ DQ
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LHV-gV-vyf">
<rect key="frame" x="187" y="182" width="219" height="17"/>
<rect key="frame" x="187" y="180" width="219" height="13"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="LUu-Vd-peN"/>
</constraints>
@@ -357,7 +355,7 @@ DQ
</textFieldCell>
</textField>
<box horizontalHuggingPriority="750" boxType="custom" borderType="line" title="Line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="168" y="132" width="1" height="207"/>
<rect key="frame" x="168" y="132" width="1" height="191"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
@@ -366,7 +364,7 @@ DQ
<font key="titleFont" metaFont="system"/>
</box>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="322" width="315" height="17"/>
<rect key="frame" x="187" y="307" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Pav-ZA-iAu"/>
</constraints>
@@ -384,7 +382,7 @@ DQ
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="322" width="142" height="17"/>
<rect key="frame" x="8" y="307" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="8mA-zi-Ev7"/>
</constraints>
@@ -405,7 +403,7 @@ DQ
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="229" y="408" width="82" height="41"/>
<rect key="frame" x="229" y="392" width="82" height="40"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>

View File

@@ -16,6 +16,7 @@
@interface SNTAboutWindowController : NSWindowController
@property IBOutlet NSTextField *aboutTextField;
@property IBOutlet NSButton *moreInfoButton;
- (IBAction)openMoreInfoURL:(id)sender;

View File

@@ -24,7 +24,12 @@
- (void)loadWindow {
[super loadWindow];
if (![[SNTConfigurator configurator] moreInfoURL]) {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *aboutText = [config aboutText];
if (aboutText) {
[self.aboutTextField setStringValue:aboutText];
}
if (![config moreInfoURL]) {
[self.moreInfoButton removeFromSuperview];
}
}

View File

@@ -0,0 +1,65 @@
/// 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 <Cocoa/Cocoa.h>
#import "Source/santa/SNTMessageWindowController.h"
@class SNTStoredEvent;
///
/// Controller for a single message window.
///
@interface SNTBinaryMessageWindowController : SNTMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
- (IBAction)showCertInfo:(id)sender;
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash;
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashLabel;
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashTitle;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// The execution event that this window is for
///
@property(readonly) SNTStoredEvent *event;
///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSProgress *progress;
@end

View File

@@ -0,0 +1,176 @@
/// 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 "Source/santa/SNTBinaryMessageWindowController.h"
#import <MOLCertificate/MOLCertificate.h>
#import <SecurityInterface/SFCertificatePanel.h>
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santa/SNTMessageWindow.h"
@interface SNTBinaryMessageWindowController ()
/// The custom message to display for this event
@property(copy) NSString *customMessage;
/// A 'friendly' string representing the certificate information
@property(readonly, nonatomic) NSString *publisherInfo;
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property(weak) IBOutlet NSTextField *applicationNameLabel;
@end
@implementation SNTBinaryMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
self = [super initWithWindowNibName:@"MessageWindow"];
if (self) {
_event = event;
_customMessage = message;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
if (progress.fractionCompleted != 0.0) {
self.hashingIndicator.indeterminate = NO;
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
if (![[SNTConfigurator configurator] eventDetailURL]) {
[self.openEventButton removeFromSuperview];
} else {
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
if (eventDetailText) {
[self.openEventButton setTitle:eventDetailText];
}
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];
[self.foundFileCountLabel removeFromSuperview];
} else {
self.openEventButton.enabled = NO;
self.hashingIndicator.indeterminate = YES;
[self.hashingIndicator startAnimation:self];
self.bundleHashLabel.hidden = YES;
self.foundFileCountLabel.stringValue = @"";
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
}
- (NSString *)messageHash {
return self.event.fileSHA256;
}
- (IBAction)showCertInfo:(id)sender {
// SFCertificatePanel expects an NSArray of SecCertificateRef's
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
for (MOLCertificate *cert in self.event.signingChain) {
[certArray addObject:(id)cert.certRef];
}
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
modalDelegate:nil
didEndSelector:nil
contextInfo:nil
certificates:certArray
showGroup:YES];
}
- (IBAction)openEventDetails:(id)sender {
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
[self closeWindow:sender];
[[NSWorkspace sharedWorkspace] openURL:url];
}
#pragma mark Generated properties
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if (![key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return [NSSet set];
}
}
- (NSString *)publisherInfo {
MOLCertificate *leafCert = [self.event.signingChain firstObject];
if (leafCert.commonName && leafCert.orgName) {
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
} else if (leafCert.commonName) {
return leafCert.commonName;
} else if (leafCert.orgName) {
return leafCert.orgName;
} else {
return nil;
}
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
customMessage:self.customMessage];
}
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
// UI updates must happen on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.event.idx isEqual:event.idx]) {
if (bundleHash) {
[self.bundleHashLabel setHidden:NO];
} else {
[self.bundleHashLabel removeFromSuperview];
[self.bundleHashTitle removeFromSuperview];
}
self.event.fileBundleHash = bundleHash;
[self.foundFileCountLabel removeFromSuperview];
[self.hashingIndicator setHidden:YES];
[self.openEventButton setEnabled:YES];
}
});
}
@end

View File

@@ -0,0 +1,38 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Cocoa/Cocoa.h>
#import "Source/common/SNTDeviceEvent.h"
#import "Source/santa/SNTMessageWindowController.h"
NS_ASSUME_NONNULL_BEGIN
@class SNTStoredEvent;
///
/// Controller for a single message window.
///
@interface SNTDeviceMessageWindowController : SNTMessageWindowController
@property(weak) IBOutlet NSTextField *remountArgsLabel;
@property(weak) IBOutlet NSTextField *remountArgsTitle;
// The device event this window is for.
@property(readonly) SNTDeviceEvent *event;
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,57 @@
/// 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 "Source/santa/SNTDeviceMessageWindowController.h"
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/santa/SNTMessageWindow.h"
NS_ASSUME_NONNULL_BEGIN
@interface SNTDeviceMessageWindowController ()
@property(copy, nullable) NSString *customMessage;
@end
@implementation SNTDeviceMessageWindowController
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message {
self = [super initWithWindowNibName:@"DeviceMessageWindow"];
if (self) {
_event = event;
_customMessage = message;
}
return self;
}
- (void)loadWindow {
[super loadWindow];
if (!self.event.remountArgs || [self.event.remountArgs count] <= 0) {
[self.remountArgsLabel removeFromSuperview];
[self.remountArgsTitle removeFromSuperview];
}
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage formatMessage:self.customMessage];
}
- (NSString *)messageHash {
return self.event.mntonname;
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,73 +1,21 @@
/// 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 <Cocoa/Cocoa.h>
@class SNTStoredEvent;
@protocol SNTMessageWindowControllerDelegate
- (void)windowDidCloseSilenceHash:(NSString *)hash;
@end
///
/// Controller for a single message window.
///
@interface SNTMessageWindowController : NSWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
- (IBAction)showWindow:(id)sender;
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashLabel;
/// Generate a distinct key for a given displayed event. This key is used for silencing future
/// notifications.
- (NSString *)messageHash;
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashTitle;
/// Linked to checkbox in UI to prevent future notifications for the given event.
@property BOOL silenceFutureNotifications;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// The execution event that this window is for
///
@property(readonly) SNTStoredEvent *event;
///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSProgress *progress;
///
/// The delegate to inform when the notification is dismissed
///
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
@end

View File

@@ -1,117 +1,13 @@
/// 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 "Source/santa/SNTMessageWindowController.h"
#import <MOLCertificate/MOLCertificate.h>
#import <SecurityInterface/SFCertificatePanel.h>
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santa/SNTMessageWindow.h"
@interface SNTMessageWindowController ()
/// The custom message to display for this event
@property(copy) NSString *customMessage;
/// A 'friendly' string representing the certificate information
@property(readonly, nonatomic) NSString *publisherInfo;
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property(weak) IBOutlet NSTextField *applicationNameLabel;
/// Linked to checkbox in UI to prevent future notifications for this binary.
@property BOOL silenceFutureNotifications;
@end
@implementation SNTMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
self = [super initWithWindowNibName:@"MessageWindow"];
if (self) {
_event = event;
_customMessage = message;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
if (progress.fractionCompleted != 0.0) {
self.hashingIndicator.indeterminate = NO;
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
if (![[SNTConfigurator configurator] eventDetailURL]) {
[self.openEventButton removeFromSuperview];
} else {
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
if (eventDetailText) {
[self.openEventButton setTitle:eventDetailText];
}
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];
[self.foundFileCountLabel removeFromSuperview];
} else {
self.openEventButton.enabled = NO;
self.hashingIndicator.indeterminate = YES;
[self.hashingIndicator startAnimation:self];
self.bundleHashLabel.hidden = YES;
self.foundFileCountLabel.stringValue = @"";
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
}
- (IBAction)showWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeIn:sender];
}
- (IBAction)closeWindow:(id)sender {
[self.progress cancel];
[(SNTMessageWindow *)self.window fadeOut:sender];
}
@@ -119,60 +15,21 @@
if (!self.delegate) return;
if (self.silenceFutureNotifications) {
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
[self.delegate windowDidCloseSilenceHash:[self messageHash]];
} else {
[self.delegate windowDidCloseSilenceHash:nil];
}
}
- (IBAction)showCertInfo:(id)sender {
// SFCertificatePanel expects an NSArray of SecCertificateRef's
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
for (MOLCertificate *cert in self.event.signingChain) {
[certArray addObject:(id)cert.certRef];
}
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
modalDelegate:nil
didEndSelector:nil
contextInfo:nil
certificates:certArray
showGroup:YES];
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
}
- (IBAction)openEventDetails:(id)sender {
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
[self closeWindow:sender];
[[NSWorkspace sharedWorkspace] openURL:url];
}
#pragma mark Generated properties
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if (![key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return [NSSet set];
}
}
- (NSString *)publisherInfo {
MOLCertificate *leafCert = [self.event.signingChain firstObject];
if (leafCert.commonName && leafCert.orgName) {
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
} else if (leafCert.commonName) {
return leafCert.commonName;
} else if (leafCert.orgName) {
return leafCert.orgName;
} else {
return nil;
}
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
customMessage:self.customMessage];
- (NSString *)messageHash {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
@end

View File

@@ -15,6 +15,8 @@
#import <Cocoa/Cocoa.h>
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/santa/SNTBinaryMessageWindowController.h"
#import "Source/santa/SNTDeviceMessageWindowController.h"
#import "Source/santa/SNTMessageWindowController.h"
///

View File

@@ -15,13 +15,16 @@
#import "Source/santa/SNTNotificationManager.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <UserNotifications/UserNotifications.h>
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santa/SNTMessageWindowController.h"
@interface SNTNotificationManager ()
@@ -56,14 +59,8 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
if ([self.pendingNotifications count]) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
if (self.pendingNotifications.count) {
[self showQueuedWindow];
} else {
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
[bc resume];
@@ -85,109 +82,67 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
[ud setObject:d forKey:silencedNotificationsKey];
}
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
NSString *customMsg;
switch (clientmode) {
case SNTClientModeMonitor:
un.informativeText = @"Switching into Monitor mode";
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
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];
if (!customMsg) break;
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
break;
default: return;
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
for (SNTMessageWindowController *msg in self.pendingNotifications) {
if ([[msg messageHash] isEqual:[pendingMsg messageHash]]) return YES;
}
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
return NO;
}
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
// See if this binary is already in the list of pending notifications.
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
NSString *messageHash = [pendingMsg messageHash];
if ([self notificationAlreadyQueued:pendingMsg]) return;
// See if this binary is silenced.
// See if this message is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
[self updateSilenceDate:nil forHash:messageHash];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
[self updateSilenceDate:nil forHash:messageHash];
} else {
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
LOGI(@"Notification silence: dropping notification for %@", messageHash);
return;
}
}
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
if (!self.currentWindowController) {
[self showQueuedWindow];
}
}
- (void)showQueuedWindow {
// Notifications arrive on a background thread but UI updates must happen on the main thread.
// This includes making windows.
dispatch_async(dispatch_get_main_queue(), ^{
SNTMessageWindowController *pendingMsg =
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
// If a notification isn't currently being displayed, display the incoming one.
// This check will generally be redundant, as we'd generally want to check this prior to
// starting work on the main thread.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
if (self.currentWindowController.event.needsBundleHash) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
SNTBinaryMessageWindowController *controller =
(SNTBinaryMessageWindowController *)self.currentWindowController;
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
[self hashBundleBinariesForEvent:controller.event withController:controller];
});
}
}
});
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
un.informativeText = message ?: @"Requested application can now be run";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
#pragma mark SNTBundleNotifierXPC protocol methods
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount {
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
dispatch_async(dispatch_get_main_queue(), ^{
self.currentWindowController.foundFileCountLabel.stringValue =
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
});
}
}
#pragma mark SNTBundleNotifierXPC helper methods
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event {
self.currentWindowController.foundFileCountLabel.stringValue = @"Searching for files...";
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
withController:(SNTBinaryMessageWindowController *)withController {
withController.foundFileCountLabel.stringValue = @"Searching for files...";
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
@@ -199,22 +154,26 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
// Wait a max of 5 secs for the bundle service
// Otherwise abandon bundle hashing and display the blockable event.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self updateBlockNotification:event withBundleHash:nil];
[withController updateBlockNotification:event withBundleHash:nil];
LOGE(@"Timeout connecting to bundle service");
return;
}
[[bc remoteObjectProxy] setNotificationListener:self.notificationListener];
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
[self.currentWindowController.progress becomeCurrentWithPendingUnitCount:100];
[withController.progress becomeCurrentWithPendingUnitCount:100];
// Start hashing. Progress is reported to the root NSProgress (currentWindowController.progress).
// Start hashing. Progress is reported to the root NSProgress
// (currentWindowController.progress).
[[bc remoteObjectProxy]
hashBundleBinariesForEvent:event
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
// Revert to displaying the blockable event if we fail to calculate the
// bundle hash
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
if (!bh)
return [withController updateBlockNotification:event
withBundleHash:nil];
event.fileBundleHash = bh;
event.fileBundleBinaryCount = @(events.count);
@@ -238,29 +197,111 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
// Update the UI with the bundle hash. Also make the openEventButton
// available.
[self updateBlockNotification:event withBundleHash:bh];
[withController updateBlockNotification:event withBundleHash:bh];
[bc invalidate];
}];
[self.currentWindowController.progress resignCurrent];
[withController.progress resignCurrent];
}
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
if (bundleHash) {
[self.currentWindowController.bundleHashLabel setHidden:NO];
} else {
[self.currentWindowController.bundleHashLabel removeFromSuperview];
[self.currentWindowController.bundleHashTitle removeFromSuperview];
}
self.currentWindowController.event.fileBundleHash = bundleHash;
[self.currentWindowController.foundFileCountLabel removeFromSuperview];
[self.currentWindowController.hashingIndicator setHidden:YES];
[self.currentWindowController.openEventButton setEnabled:YES];
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Santa";
switch (clientmode) {
case SNTClientModeMonitor: {
content.body = @"Switching into Monitor mode";
NSString *customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
if (!customMsg) break;
// If a custom message is added but as an empty string, disable notifications.
if (!customMsg.length) return;
content.body = [SNTBlockMessage stringFromHTML:customMsg];
break;
}
});
case SNTClientModeLockdown: {
content.body = @"Switching into Lockdown mode";
NSString *customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
if (!customMsg) break;
// If a custom message is added but as an empty string, disable notifications.
if (!customMsg.length) return;
content.body = [SNTBlockMessage stringFromHTML:customMsg];
break;
}
default: return;
}
UNNotificationRequest *req =
[UNNotificationRequest requestWithIdentifier:@"clientModeNotification"
content:content
trigger:nil];
[un addNotificationRequest:req withCompletionHandler:nil];
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Santa";
content.body = message ?: @"Requested application can now be run";
NSString *identifier = [NSString stringWithFormat:@"ruleSyncNotification_%@", content.body];
UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:identifier
content:content
trigger:nil];
[un addNotificationRequest:req withCompletionHandler:nil];
}
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
SNTBinaryMessageWindowController *pendingMsg =
[[SNTBinaryMessageWindowController alloc] initWithEvent:event andMessage:message];
[self queueMessage:pendingMsg];
}
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
SNTDeviceMessageWindowController *pendingMsg =
[[SNTDeviceMessageWindowController alloc] initWithEvent:event message:message];
[self queueMessage:pendingMsg];
}
#pragma mark SNTBundleNotifierXPC protocol methods
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount {
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
SNTBinaryMessageWindowController *controller =
(SNTBinaryMessageWindowController *)self.currentWindowController;
if ([controller.event.idx isEqual:event.idx]) {
dispatch_async(dispatch_get_main_queue(), ^{
controller.foundFileCountLabel.stringValue =
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
});
}
}
}
@end

View File

@@ -2,7 +2,15 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>EQHXZ8M8AV.com.google.santa</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.developer.team-identifier</key>
<string>EQHXZ8M8AV</string>
<key>keychain-access-groups</key>
<array>
<string>EQHXZ8M8AV.com.google.santa</string>
</array>
</dict>
</plist>

View File

@@ -67,10 +67,6 @@ int main(int argc, const char *argv[]) {
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
OSSystemExtensionRequest *req;
if (sysxOperation.intValue == 1) {
if (![[SNTConfigurator configurator] enableSystemExtension]) {
NSLog(@"EnableSystemExtension is disabled");
exit(1);
}
NSLog(@"Requesting SystemExtension activation");
req = [OSSystemExtensionRequest activationRequestForExtension:e queue:q];
} else if (sysxOperation.intValue == 2) {

View File

@@ -1,93 +0,0 @@
load(
"@build_bazel_rules_apple//apple:macos.bzl",
"macos_command_line_application",
"macos_kernel_extension",
)
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
licenses(["notice"])
cc_library(
name = "santa_driver_lib",
srcs = [
"SantaDecisionManager.cc",
"SantaDecisionManager.h",
"SantaDriver.cc",
"SantaDriver.h",
"SantaDriverClient.cc",
"SantaDriverClient.h",
"main.cc",
],
copts = [
"-mkernel",
"-fapple-kext",
"-Wno-ossharedptr-misuse",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = [
"KERNEL",
"APPLE",
"NeXT",
"SANTA_VERSION=" + SANTA_VERSION,
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLoggingKernel",
"//Source/common:SNTPrefixTreeKernel",
],
alwayslink = 1,
)
macos_kernel_extension(
name = "santa_driver",
bundle_id = "com.google.santa-driver",
bundle_name = "santa-driver",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santa_driver_lib"],
)
objc_library(
name = "kernel_tests_lib",
srcs = ["kernel_tests.mm"],
sdk_frameworks = [
"Foundation",
"IOKit",
],
deps = ["//Source/common:SNTKernelCommon"],
)
macos_command_line_application(
name = "kernel_tests_bin",
bundle_id = "com.google.santa.KernelTests",
minimum_os_version = "10.9",
deps = [":kernel_tests_lib"],
)
run_command(
name = "kernel_tests",
srcs = [
":kernel_tests_bin",
":santa_driver",
],
cmd = """
env
function sigint() {
echo "\nInterrupted, unloading driver."
sudo kextunload -b com.google.santa-driver >/dev/null
exit 1
}
unzip -o $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
echo "Launching Kernel Tests as root. You may be prompted for your sudo password."
trap sigint INT
sudo $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/kernel_tests_bin
echo "Tests complete."
if kextstat | grep com.google.santa-driver; then
sudo kextunload -b com.google.santa-driver >/dev/null
fi
""",
)

View File

@@ -1,51 +0,0 @@
<?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>CFBundleExecutable</key>
<string>santa-driver</string>
<key>CFBundleIdentifier</key>
<string>com.google.santa-driver</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>santa-driver</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>IOKitPersonalities</key>
<dict>
<key>SantaDriver</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.google.santa-driver</string>
<key>IOClass</key>
<string>com_google_SantaDriver</string>
<key>IOMatchCategory</key>
<string>com_google_SantaDriver</string>
<key>IOProviderClass</key>
<string>IOResources</string>
<key>IOResourceMatch</key>
<string>IOKit</string>
<key>IOUserClientClass</key>
<string>com_google_SantaDriverClient</string>
</dict>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Google LLC.</string>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.bsd</key>
<string>9.0.0</string>
<key>com.apple.kpi.iokit</key>
<string>9.0.0</string>
<key>com.apple.kpi.libkern</key>
<string>9.0.0</string>
<key>com.apple.kpi.mach</key>
<string>9.0.0</string>
</dict>
</dict>
</plist>

View File

@@ -1,843 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santa_driver/SantaDecisionManager.h"
// This is a made-up KAUTH_FILEOP constant which represents a
// KAUTH_VNODE_WRITE_DATA event that gets passed to SantaDecisionManager's
// FileOpCallback method. The KAUTH_FILEOP_* constants are defined in
// sys/kauth.h and run from 1--7. KAUTH_VNODE_WRITE_DATA is already defined as
// 4 so it overlaps with the other KAUTH_FILEOP_* constants and can't be used.
// We define KAUTH_FILEOP_WRITE as something much greater than 7.
#define KAUTH_FILEOP_WRITE 100
#define super OSObject
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
#pragma mark Object Lifecycle
template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t) {
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
}
bool SantaDecisionManager::init() {
if (!super::init()) return false;
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
sdm_lock_attr_ = lck_attr_alloc_init();
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(5000, 2);
non_root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(500, 2);
vnode_pid_map_ = new SantaCache<santa_vnode_id_t, uint64_t>(2000, 5);
compiler_pid_set_ = new SantaCache<pid_t, pid_t>(500, 5);
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
if (!decision_dataqueue_) return kIOReturnNoMemory;
log_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxLogQueueEvents, sizeof(santa_message_t));
if (!log_dataqueue_) return kIOReturnNoMemory;
client_pid_ = 0;
root_fsid_ = 0;
// Setup file modification prefix filter.
filemod_prefix_filter_ = new SNTPrefixTree();
return true;
}
void SantaDecisionManager::free() {
delete root_decision_cache_;
delete non_root_decision_cache_;
delete vnode_pid_map_;
StopPidMonitorThreads();
if (decision_dataqueue_lock_) {
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
decision_dataqueue_lock_ = nullptr;
}
if (log_dataqueue_lock_) {
lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
log_dataqueue_lock_ = nullptr;
}
if (sdm_lock_attr_) {
lck_attr_free(sdm_lock_attr_);
sdm_lock_attr_ = nullptr;
}
if (sdm_lock_grp_) {
lck_grp_free(sdm_lock_grp_);
sdm_lock_grp_ = nullptr;
}
if (sdm_lock_grp_attr_) {
lck_grp_attr_free(sdm_lock_grp_attr_);
sdm_lock_grp_attr_ = nullptr;
}
OSSafeReleaseNULL(decision_dataqueue_);
OSSafeReleaseNULL(log_dataqueue_);
delete filemod_prefix_filter_;
super::free();
}
#pragma mark Client Management
void SantaDecisionManager::ConnectClient(pid_t pid) {
if (!pid) return;
client_pid_ = pid;
// Determine root fsid
vfs_context_t ctx = vfs_context_create(nullptr);
if (ctx) {
vnode_t root = vfs_rootvnode();
if (root) {
root_fsid_ = GetVnodeIDForVnode(ctx, root).fsid;
vnode_put(root);
}
vfs_context_rele(ctx);
}
// Any decisions made while the daemon wasn't
// connected should be cleared
ClearCache();
failed_decision_queue_requests_ = 0;
failed_log_queue_requests_ = 0;
}
void SantaDecisionManager::DisconnectClient(bool itDied, pid_t pid) {
if (client_pid_ == 0 || (pid > 0 && pid != client_pid_)) return;
client_pid_ = 0;
// Ask santad to shutdown, in case it's running.
if (!itDied) {
auto message = new santa_message_t;
message->action = ACTION_REQUEST_SHUTDOWN;
PostToDecisionQueue(message);
delete message;
decision_dataqueue_->setNotificationPort(nullptr);
} else {
// If the client died, reset the data queues so when it reconnects
// it doesn't get swamped straight away.
lck_mtx_lock(decision_dataqueue_lock_);
decision_dataqueue_->release();
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
lck_mtx_unlock(decision_dataqueue_lock_);
lck_mtx_lock(log_dataqueue_lock_);
log_dataqueue_->release();
log_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxLogQueueEvents, sizeof(santa_message_t));
lck_mtx_unlock(log_dataqueue_lock_);
}
// Reset the filter.
// On startup the daemon will add prefixes as needed.
FilemodPrefixFilterReset();
}
bool SantaDecisionManager::ClientConnected() const {
if (client_pid_ <= 0) return false;
auto p = proc_find(client_pid_);
auto is_exiting = false;
if (p) {
is_exiting = proc_exiting(p);
proc_rele(p);
}
return (client_pid_ > 0 && !is_exiting);
}
void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
lck_mtx_lock(decision_dataqueue_lock_);
decision_dataqueue_->setNotificationPort(port);
lck_mtx_unlock(decision_dataqueue_lock_);
}
void SantaDecisionManager::SetLogPort(mach_port_t port) {
lck_mtx_lock(log_dataqueue_lock_);
log_dataqueue_->setNotificationPort(port);
lck_mtx_unlock(log_dataqueue_lock_);
}
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
lck_mtx_lock(decision_dataqueue_lock_);
IOMemoryDescriptor *r = decision_dataqueue_->getMemoryDescriptor();
lck_mtx_unlock(decision_dataqueue_lock_);
return r;
}
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
lck_mtx_lock(log_dataqueue_lock_);
IOMemoryDescriptor *r = log_dataqueue_->getMemoryDescriptor();
lck_mtx_unlock(log_dataqueue_lock_);
return r;
}
#pragma mark Listener Control
kern_return_t SantaDecisionManager::StartListener() {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
vnode_listener_ = kauth_listen_scope(
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
#pragma clang diagnostic pop
if (!vnode_listener_) return kIOReturnInternalError;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
fileop_listener_ = kauth_listen_scope(
KAUTH_SCOPE_FILEOP, fileop_scope_callback,
reinterpret_cast<void *>(this));
#pragma clang diagnostic pop
if (!fileop_listener_) return kIOReturnInternalError;
LOGD("Listeners started.");
return kIOReturnSuccess;
}
kern_return_t SantaDecisionManager::StopListener() {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
kauth_unlisten_scope(vnode_listener_);
vnode_listener_ = nullptr;
kauth_unlisten_scope(fileop_listener_);
fileop_listener_ = nullptr;
#pragma clang diagnostic pop
// Wait for any active invocations to finish before returning
do {
IOSleep(5);
} while (listener_invocations_);
// Delete any cached decisions
ClearCache();
LOGD("Listeners stopped.");
return kIOReturnSuccess;
}
# pragma mark Monitoring PIDs
// Arguments that are passed to pid_monitor thread.
typedef struct {
pid_t pid; // process to monitor
SantaDecisionManager *sdm; // reference to SantaDecisionManager
} pid_monitor_info;
// Function executed in its own thread used to monitor a compiler process for
// termination and then remove the process pid from cache of compiler pids.
static void pid_monitor(void *param, __unused wait_result_t wait_result) {
pid_monitor_info *info = (pid_monitor_info *)param;
if (info && info->sdm) {
uint32_t sleep_time = info->sdm->PidMonitorSleepTimeMilliseconds();
while (!info->sdm->PidMonitorThreadsShouldExit()) {
proc_t proc = proc_find(info->pid);
if (!proc) break;
proc_rele(proc);
IOSleep(sleep_time);
}
info->sdm->ForgetCompilerPid(info->pid);
info->sdm->DecrementPidMonitorThreadCount();
}
thread_terminate(current_thread());
}
// TODO(nguyenphillip): Look at moving pid monitoring out of SDM entirely,
// maybe by creating a dedicated class to do this that SDM could then query.
void SantaDecisionManager::MonitorCompilerPidForExit(pid_t pid) {
// Don't start any new threads if compiler_pid_set_ doesn't exist.
if (!compiler_pid_set_) return;
auto info = new pid_monitor_info;
info->pid = pid;
info->sdm = this;
thread_t thread = THREAD_NULL;
IncrementPidMonitorThreadCount();
if (KERN_SUCCESS != kernel_thread_start(pid_monitor, (void *)info, &thread)) {
LOGE("couldn't start pid monitor thread");
DecrementPidMonitorThreadCount();
}
thread_deallocate(thread);
}
void SantaDecisionManager::ForgetCompilerPid(pid_t pid) {
if (compiler_pid_set_) compiler_pid_set_->remove(pid);
}
bool SantaDecisionManager::PidMonitorThreadsShouldExit() const {
return compiler_pid_set_ == nullptr;
}
bool SantaDecisionManager::StopPidMonitorThreads() {
// Each pid_monitor thread checks for the existence of compiler_pid_set_.
// As soon as they see that it's gone, they should terminate and decrement
// SantaDecisionManager's pid_monitor_thread_count. When this count decreases
// to zero all threads have finished.
auto temp = compiler_pid_set_;
compiler_pid_set_ = nullptr;
delete temp;
// Sleep time between checks starts at 10 ms, but increases to 5 sec after
// 10 sec have passed without the thread count dropping to 0.
unsigned int sleep_time_milliseconds = 10;
unsigned int total_wait_time = 0;
while (pid_monitor_thread_count_ > 0) {
if (sleep_time_milliseconds == 10) {
total_wait_time += sleep_time_milliseconds;
if (total_wait_time >= 10000) {
sleep_time_milliseconds = 5000;
LOGD("Waited %d ms for pid monitor threads to quit, switching sleep"
"time to %d ms", total_wait_time, sleep_time_milliseconds);
}
}
IOSleep(sleep_time_milliseconds);
}
LOGD("Pid monitor threads stopped.");
return true;
}
uint32_t SantaDecisionManager::PidMonitorSleepTimeMilliseconds() const {
return kPidMonitorSleepTimeMilliseconds;
}
#pragma mark Cache Management
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<santa_vnode_id_t, uint64_t> *SantaDecisionManager::CacheForIdentifier(
const santa_vnode_id_t identifier) {
return (identifier.fsid == root_fsid_) ? root_decision_cache_ : non_root_decision_cache_;
}
void SantaDecisionManager::AddToCache(
santa_vnode_id_t identifier, santa_action_t decision, uint64_t microsecs) {
auto decision_cache = CacheForIdentifier(identifier);
switch (decision) {
case ACTION_REQUEST_BINARY:
decision_cache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
break;
case ACTION_RESPOND_ACK:
decision_cache->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_COMPILER:
case ACTION_RESPOND_DENY: {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
if (!decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
break;
}
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
decision_cache->set(identifier, val, 0);
break;
}
default:
break;
}
wakeup((void *)identifier.unsafe_simple_id());
}
void SantaDecisionManager::RemoveFromCache(santa_vnode_id_t identifier) {
if (unlikely(identifier.fsid == 0 && identifier.fileid == 0)) return;
CacheForIdentifier(identifier)->remove(identifier);
wakeup((void *)identifier.unsafe_simple_id());
}
uint64_t SantaDecisionManager::RootCacheCount() const {
return root_decision_cache_->count();
}
uint64_t SantaDecisionManager::NonRootCacheCount() const {
return non_root_decision_cache_->count();
}
void SantaDecisionManager::ClearCache(bool non_root_only) {
if (!non_root_only) root_decision_cache_->clear();
non_root_decision_cache_->clear();
}
void SantaDecisionManager::CacheBucketCount(
uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
root_decision_cache_->bucket_counts(per_bucket_counts, array_size, start_bucket);
}
#pragma mark Decision Fetching
santa_action_t SantaDecisionManager::GetFromCache(santa_vnode_id_t identifier) {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
auto decision_cache = CacheForIdentifier(identifier);
uint64_t cache_val = decision_cache->get(identifier);
if (cache_val == 0) return result;
// Decision is stored in upper 8 bits, timestamp in remaining 56.
result = (santa_action_t)(cache_val >> 56);
decision_time = (cache_val & ~(0xFF00000000000000));
if (RESPONSE_VALID(result)) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache->remove(identifier);
return ACTION_UNSET;
}
}
}
return result;
}
santa_action_t SantaDecisionManager::GetFromDaemon(
santa_message_t *message, santa_vnode_id_t identifier) {
auto return_action = ACTION_UNSET;
#ifdef DEBUG
clock_sec_t secs = 0;
clock_usec_t microsecs = 0;
clock_get_system_microtime(&secs, &microsecs);
uint64_t uptime = (secs * 1000000) + microsecs;
#endif
// Wait for the daemon to respond or die.
do {
// Add pending request to cache, to be replaced
// by daemon with actual response.
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
// Send request to daemon.
if (!PostToDecisionQueue(message)) {
LOGE("Failed to queue request for %s.", message->path);
RemoveFromCache(identifier);
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.unsafe_simple_id(), NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} 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
if (!RESPONSE_VALID(return_action)) {
LOGE("Daemon process did not respond correctly. Allowing executions "
"until it comes back. Executable path: %s", message->path);
RemoveFromCache(identifier);
return ACTION_ERROR;
}
#ifdef DEBUG
clock_get_system_microtime(&secs, &microsecs);
LOGD("Decision time: %4lldms (%s)",
(((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
#endif
return return_action;
}
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t cred,
const vnode_t vp,
const santa_vnode_id_t vnode_id) {
while (true) {
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
// Check to see if item is in cache
auto return_action = GetFromCache(vnode_id);
// If item was in cache with a valid response, return it.
// If item is in cache but hasn't received a response yet, sleep for a bit.
// If item is not in cache, break out of loop to send request to daemon.
if (RESPONSE_VALID(return_action)) {
return return_action;
} else if (return_action == ACTION_REQUEST_BINARY || 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.unsafe_simple_id(), NULL, 0, "", &ts_);
} else {
break;
}
}
// Get path
char path[MAXPATHLEN];
int name_len = MAXPATHLEN;
path[MAXPATHLEN - 1] = 0;
if (vn_getpath(vp, path, &name_len) == ENOSPC) {
return ACTION_RESPOND_TOOLONG;
}
auto message = NewMessage(cred);
strlcpy(message->path, path, sizeof(message->path));
message->action = ACTION_REQUEST_BINARY;
message->vnode_id = vnode_id;
proc_name(message->ppid, message->pname, sizeof(message->pname));
auto return_action = GetFromDaemon(message, vnode_id);
delete message;
return return_action;
}
#pragma mark Misc
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
lck_mtx_lock(decision_dataqueue_lock_);
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (++failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
LOGE("Failed to queue more than %d decision requests, killing daemon",
kMaxDecisionQueueFailures);
proc_signal(client_pid_, SIGKILL);
}
}
lck_mtx_unlock(decision_dataqueue_lock_);
return kr;
}
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
lck_mtx_lock(log_dataqueue_lock_);
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (failed_log_queue_requests_++ == 0) {
LOGW("Dropping log queue messages");
}
} else {
if (failed_log_queue_requests_ > 0) {
failed_log_queue_requests_--;
}
}
lck_mtx_unlock(log_dataqueue_lock_);
return kr;
}
#pragma mark Invocation Tracking & PID comparison
void SantaDecisionManager::IncrementListenerInvocations() {
OSIncrementAtomic(&listener_invocations_);
}
void SantaDecisionManager::DecrementListenerInvocations() {
OSDecrementAtomic(&listener_invocations_);
}
void SantaDecisionManager::IncrementPidMonitorThreadCount() {
OSIncrementAtomic(&pid_monitor_thread_count_);
}
void SantaDecisionManager::DecrementPidMonitorThreadCount() {
OSDecrementAtomic(&pid_monitor_thread_count_);
}
bool SantaDecisionManager::IsCompilerProcess(pid_t pid) {
for (;;) {
// Find the parent pid.
proc_t proc = proc_find(pid);
if (!proc) return false;
pid_t ppid = proc_ppid(proc);
proc_rele(proc);
// Quit if process is launchd or has no parent.
if (ppid == 0 || pid == ppid) break;
pid_t val = compiler_pid_set_->get(pid);
// If pid was in compiler_pid_set_ then make sure that it has the same
// parent pid as when it was set.
if (val) return val == ppid;
// If pid not in the set, then quit unless we want to check ancestors.
if (!kCheckCompilerAncestors) break;
pid = ppid;
}
return false;
}
#pragma mark Callbacks
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
const vfs_context_t ctx,
const vnode_t vp,
int *errno) {
// Get ID for the vnode
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
if (vnode_id.fsid == 0 && vnode_id.fileid == 0) return KAUTH_RESULT_DEFER;
// Fetch decision
auto returnedAction = FetchDecision(cred, vp, vnode_id);
switch (returnedAction) {
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_COMPILER:
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
auto proc = vfs_context_proc(ctx);
if (proc) {
pid_t pid = proc_pid(proc);
pid_t ppid = proc_ppid(proc);
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
uint64_t val = ((uint64_t)pid << 32) | ((uint64_t)ppid & 0xFFFFFFFF);
vnode_pid_map_->set(vnode_id, val);
if (returnedAction == ACTION_RESPOND_ALLOW_COMPILER && ppid != 0) {
// Do some additional bookkeeping for compilers:
// We associate the pid with a compiler so that when we see it later
// in the context of a KAUTH_FILEOP event, we'll recognize it.
compiler_pid_set_->set(pid, ppid);
// And start polling for the compiler process termination, so that we
// can remove the pid from our cache of compiler pids.
MonitorCompilerPidForExit(pid);
}
}
return KAUTH_RESULT_ALLOW;
}
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
// we don't break user's machines.
return KAUTH_RESULT_DEFER;
}
}
void SantaDecisionManager::FileOpCallback(
const kauth_action_t action, const vnode_t vp,
const char *path, const char *new_path) {
if (!ClientConnected()) return;
// KAUTH_FILEOP_CLOSE implies KAUTH_FILEOP_CLOSE_MODIFIED, so remove it from the cache.
if (action == KAUTH_FILEOP_CLOSE) {
auto context = vfs_context_create(nullptr);
RemoveFromCache(GetVnodeIDForVnode(context, vp));
vfs_context_rele(context);
}
// Don't log santad fileops.
if (proc_selfpid() == client_pid_) return;
if (vp && action == KAUTH_FILEOP_EXEC) {
auto context = vfs_context_create(nullptr);
auto vnode_id = GetVnodeIDForVnode(context, vp);
vfs_context_rele(context);
auto message = NewMessage(nullptr);
message->vnode_id = vnode_id;
message->action = ACTION_NOTIFY_EXEC;
strlcpy(message->path, path, sizeof(message->path));
// The vnode scope gets posix_spawn pid and ppid properly. The fileop scope does not.
// Get pid and ppid cached during vnode execution.
uint64_t val = vnode_pid_map_->get(vnode_id);
if (val) {
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
message->pid = (pid_t)(val >> 32);
message->ppid = (pid_t)(val & ~0xFFFFFFFF00000000);
}
PostToLogQueue(message);
delete message;
return;
}
// For transitive whitelisting decisions, we must check for KAUTH_FILEOP_CLOSE events from a
// known compiler process. But we must also check for KAUTH_FILEOP_RENAME events because clang
// under Xcode 9 will, if the output file already exists, write to a temp file, delete the
// existing file, then rename the temp file, without ever closing it. So in this scenario,
// the KAUTH_FILEOP_RENAME is the only chance we have of whitelisting the output.
if (action == KAUTH_FILEOP_CLOSE || (action == KAUTH_FILEOP_RENAME && new_path)) {
auto message = NewMessage(nullptr);
if (IsCompilerProcess(message->pid)) {
// Fill out the rest of the message details and send it to the decision queue.
auto context = vfs_context_create(nullptr);
vnode_t real_vp = vp;
// We have to manually look up the vnode pointer from new_path for KAUTH_FILEOP_RENAME.
if (!real_vp && new_path && ERR_SUCCESS == vnode_lookup(new_path, 0, &real_vp, context)) {
vnode_put(real_vp);
}
if (real_vp) message->vnode_id = GetVnodeIDForVnode(context, real_vp);
vfs_context_rele(context);
message->action = ACTION_NOTIFY_WHITELIST;
const char *real_path = (action == KAUTH_FILEOP_CLOSE) ? path : new_path;
strlcpy(message->path, real_path, sizeof(message->path));
proc_name(message->pid, message->pname, sizeof(message->pname));
PostToDecisionQueue(message);
// Add a temporary allow rule to the decision cache for this vnode_id
// while SNTCompilerController decides whether or not to add a
// permanent rule for the new file to the rules database. This is
// because checking if the file is a Mach-O binary and hashing it might
// not finish before an attempt to execute it.
AddToCache(message->vnode_id, ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE, 0);
}
delete message;
// Don't need to do anything else for FILEOP_CLOSE, but FILEOP_RENAME should fall through.
if (action == KAUTH_FILEOP_CLOSE) return;
}
// Filter out modifications to locations that are definitely
// not useful or made by santad.
if (!filemod_prefix_filter_->HasPrefix(path)) {
auto message = NewMessage(nullptr);
strlcpy(message->path, path, sizeof(message->path));
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
proc_name(message->pid, message->pname, sizeof(message->pname));
switch (action) {
case KAUTH_FILEOP_WRITE:
// This is actually a KAUTH_VNODE_WRITE_DATA event.
message->action = ACTION_NOTIFY_WRITE;
break;
case KAUTH_FILEOP_RENAME:
message->action = ACTION_NOTIFY_RENAME;
break;
case KAUTH_FILEOP_LINK:
message->action = ACTION_NOTIFY_LINK;
break;
case KAUTH_FILEOP_EXCHANGE:
message->action = ACTION_NOTIFY_EXCHANGE;
break;
case KAUTH_FILEOP_DELETE:
message->action = ACTION_NOTIFY_DELETE;
break;
default:
delete message;
return;
}
PostToLogQueue(message);
delete message;
}
}
#undef super
extern "C" int fileop_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
if (unlikely(sdm == nullptr)) {
LOGE("fileop_scope_callback called with no decision manager");
return KAUTH_RESULT_DEFER;
}
vnode_t vp = nullptr;
char *path = nullptr;
char *new_path = nullptr;
switch (action) {
case KAUTH_FILEOP_CLOSE:
// We only care about KAUTH_FILEOP_CLOSE events where the closed file
// was modified.
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED))
return KAUTH_RESULT_DEFER;
// Intentional fallthrough to get vnode reference.
[[fallthrough]];
case KAUTH_FILEOP_DELETE:
case KAUTH_FILEOP_EXEC:
vp = reinterpret_cast<vnode_t>(arg0);
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
path = reinterpret_cast<char *>(arg1);
break;
case KAUTH_FILEOP_RENAME:
case KAUTH_FILEOP_EXCHANGE:
case KAUTH_FILEOP_LINK:
path = reinterpret_cast<char *>(arg0);
new_path = reinterpret_cast<char *>(arg1);
break;
default:
return KAUTH_RESULT_DEFER;
}
sdm->IncrementListenerInvocations();
sdm->FileOpCallback(action, vp, path, new_path);
sdm->DecrementListenerInvocations();
return KAUTH_RESULT_DEFER;
}
extern "C" int vnode_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
if (unlikely(sdm == nullptr)) {
LOGE("vnode_scope_callback called with no decision manager");
return KAUTH_RESULT_DEFER;
}
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
// We only care about regular files.
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
if ((action & (int)KAUTH_VNODE_EXECUTE) && !(action & (int)KAUTH_VNODE_ACCESS)) {
sdm->IncrementListenerInvocations();
int result = sdm->VnodeCallback(credential,
reinterpret_cast<vfs_context_t>(arg0),
vp,
reinterpret_cast<int *>(arg3));
sdm->DecrementListenerInvocations();
return result;
} else if (action & (int)KAUTH_VNODE_WRITE_DATA || action & (int)KAUTH_VNODE_APPEND_DATA) {
sdm->IncrementListenerInvocations();
if (!(action & (int)KAUTH_VNODE_ACCESS)) {
auto vnode_id = sdm->GetVnodeIDForVnode(reinterpret_cast<vfs_context_t>(arg0), vp);
sdm->RemoveFromCache(vnode_id);
}
char path[MAXPATHLEN];
int pathlen = MAXPATHLEN;
vn_getpath(vp, path, &pathlen);
// KAUTH_VNODE_WRITE_DATA events are translated into fake KAUTH_FILEOP_WRITE
// events so that we can handle them in the FileOpCallback function.
sdm->FileOpCallback(KAUTH_FILEOP_WRITE, vp, path, nullptr);
sdm->DecrementListenerInvocations();
}
return KAUTH_RESULT_DEFER;
}

View File

@@ -1,422 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#define SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#include <IOKit/IODataQueueShared.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOSharedDataQueue.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTLogging.h"
#include "Source/common/SNTPrefixTree.h"
#include "Source/common/SantaCache.h"
///
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
/// and responding to the request appropriately.
///
/// Documentation on the Kauth parts can be found here:
/// https://developer.apple.com/library/mac/technotes/tn2127/_index.html
///
class SantaDecisionManager : public OSObject {
OSDeclareDefaultStructors(SantaDecisionManager);
public:
/// Used for initialization after instantiation.
bool init() override;
/// Called automatically when retain count drops to 0.
void free() override;
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the decision queue.
*/
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the logging queue.
*/
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
/**
Called by SantaDriverClient when a client connects to the decision queue,
providing the pid of the client process.
*/
void ConnectClient(pid_t pid);
/// Called by SantaDriverClient when a client disconnects
void DisconnectClient(bool itDied = false, pid_t pid = proc_selfpid());
/// Returns whether a client is currently connected or not.
bool ClientConnected() const;
/// Sets the Mach port for notifying the decision queue.
void SetDecisionPort(mach_port_t port);
/// Sets the Mach port for notifying the log queue.
void SetLogPort(mach_port_t port);
/// Starts the kauth listeners.
kern_return_t StartListener();
/**
Stops the kauth listeners. After stopping new callback requests, waits
until all current invocations have finished before clearing the cache and
returning.
*/
kern_return_t StopListener();
/**
This spins off a new thread for each process that we monitor. Generally the
threads should be short-lived, since they die as soon as their associated
compiler process dies.
*/
void MonitorCompilerPidForExit(pid_t pid);
/// Remove the given pid from cache of compiler pids.
void ForgetCompilerPid(pid_t pid);
/// Returns true when SantaDecisionManager wants monitor threads to exit.
bool PidMonitorThreadsShouldExit() const;
/**
Stops the pid monitor threads. Waits until all threads have stopped before
returning. This also frees the compiler_pid_set_. Returns true if all
threads exited cleanly. Returns false if timed out while waiting.
*/
bool StopPidMonitorThreads();
/// Returns how long pid monitor should sleep between termination checks.
uint32_t PidMonitorSleepTimeMilliseconds() const;
/// Adds a decision to the cache, with a timestamp.
void AddToCache(santa_vnode_id_t identifier, 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.
*/
santa_action_t GetFromCache(santa_vnode_id_t identifier);
/// Checks to see if a given identifier is in the cache and removes it.
void RemoveFromCache(santa_vnode_id_t identifier);
/// Returns the number of entries in the cache.
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.
*/
void ClearCache(bool non_root_only = false);
/**
Fills out the per_bucket_counts array with the number of items in each
bucket in the non-root decision cache.
@param per_bucket_counts An array of uint16_t's to fill in with the number
of items in each bucket. The size of this array is expected to equal
array_size.
@param array_size The size of the per_bucket_counts array on input. Upon
return this will be updated to the number of slots that were actually used.
@param start_bucket If non-zero this is the bucket in the cache to start
from. Upon return this will be the next numbered bucket to start from for
subsequent requests.
*/
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size,
uint64_t *start_bucket);
/// Increments the count of active callbacks pending.
void IncrementListenerInvocations();
/// Decrements the count of active callbacks pending.
void DecrementListenerInvocations();
/// Increments the count of active pid monitor threads.
void IncrementPidMonitorThreadCount();
/// Decrements the count of active pid monitor threads.
void DecrementPidMonitorThreadCount();
/**
Determine if pid belongs to a compiler process. When
kCheckCompilerAncestors is set to true, this also checks all ancestor
processes of the pid.
*/
bool IsCompilerProcess(pid_t pid);
/**
Add a file modification prefix filter.
*/
inline IOReturn FilemodPrefixFilterAdd(const char *prefix,
uint64_t *node_count = nullptr) {
return filemod_prefix_filter_->AddPrefix(prefix, node_count);
}
/**
Reset the file modification prefix filter tree.
*/
inline void FilemodPrefixFilterReset() { filemod_prefix_filter_->Reset(); }
/**
Fetches the vnode_id for a given vnode.
@param ctx The VFS context to use.
@param vp The Vnode to get the ID for
@return santa_vnode_id_t The Vnode ID.
*/
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx,
const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, ctx);
return {.fsid = vap.va_fsid, .fileid = vap.va_fileid};
}
/**
Vnode Callback
@param cred The kauth credential for this request.
@param ctx The VFS context for this request.
@param vp The Vnode for this request.
@param errno A pointer to return an errno style error.
@return int A valid KAUTH_RESULT_*.
*/
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
const vnode_t vp, int *errno);
/**
FileOp Callback
@param action The performed action
@param vp The Vnode for this request. May be nullptr.
@param path The path being operated on.
@param new_path The target path for moves and links.
*/
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path,
const char *new_path);
private:
/**
While waiting for a response from the daemon, this is the maximum number of
milliseconds to sleep for before checking the cache for a response.
*/
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
/**
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.
static const uint32_t kMaxCacheSize = 10000;
/// Maximum number of PostToDecisionQueue failures to allow.
static const uint32_t kMaxDecisionQueueFailures = 10;
/**
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 that can be kept in the logging data queue
at any time.
*/
static const uint32_t kMaxLogQueueEvents = 2048;
/// How long pid monitor thread should sleep between termination checks.
static const uint32_t kPidMonitorSleepTimeMilliseconds = 1000;
/**
When set to true, Santa will check all ancestors of a process to determine
if it is a compiler.
TODO(nguyenphillip): this setting (and others above) should be configurable.
*/
static const bool kCheckCompilerAncestors = false;
/**
Fetches a response from the daemon. Handles both daemon death
and failure to post messages to the daemon.
@param message The message to send to the daemon
@param identifier The vnode ID string for this request
@return santa_action_t The response for this request
*/
santa_action_t GetFromDaemon(santa_message_t *message,
santa_vnode_id_t identifier);
/**
Fetches an execution decision for a file, first using the cache and then
by sending a message to the daemon and waiting until a response arrives.
If a daemon isn't connected, will allow execution and cache, logging
the path to the executed file.
@param cred The credential for this request.
@param vp The Vnode for this request.
@param vnode_id The ID for this vnode.
@return santa_action_t The response for this request
*/
santa_action_t FetchDecision(const kauth_cred_t cred, const vnode_t vp,
const santa_vnode_id_t vnode_id);
/**
Posts the requested message to the decision data queue.
@param message The message to send
@return bool true if sending was successful.
*/
bool PostToDecisionQueue(santa_message_t *message);
/**
Posts the requested message to the logging data queue.
@param message The message to send
@return bool true if sending was successful.
*/
bool PostToLogQueue(santa_message_t *message);
/**
Creates a new santa_message_t with some fields pre-filled.
@param credential The kauth_cred_t for this action, if available.
If nullptr, will get the credential for the current process.
*/
static inline santa_message_t *NewMessage(kauth_cred_t credential) {
bool should_release = false;
if (credential == nullptr) {
credential = kauth_cred_get_with_ref();
should_release = true;
}
auto message = new santa_message_t;
message->uid = kauth_cred_getuid(credential);
message->gid = kauth_cred_getgid(credential);
message->pid = proc_selfpid();
message->ppid = proc_selfppid();
if (should_release) {
kauth_cred_unref(&credential);
}
return message;
}
/**
Returns the current system uptime in microseconds
*/
static inline uint64_t GetCurrentUptime() {
clock_sec_t sec;
clock_usec_t usec;
clock_get_system_microtime(&sec, &usec);
return (uint64_t)((sec * 1000000) + usec);
}
SantaCache<santa_vnode_id_t, uint64_t> *root_decision_cache_;
SantaCache<santa_vnode_id_t, uint64_t> *non_root_decision_cache_;
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;
SantaCache<pid_t, pid_t> *compiler_pid_set_;
SNTPrefixTree *filemod_prefix_filter_;
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<santa_vnode_id_t, uint64_t> *CacheForIdentifier(
const santa_vnode_id_t identifier);
// This is the file system ID of the root filesystem,
// used to determine which cache to use for requests
uint64_t root_fsid_;
lck_grp_t *sdm_lock_grp_;
lck_grp_attr_t *sdm_lock_grp_attr_;
lck_attr_t *sdm_lock_attr_;
lck_mtx_t *decision_dataqueue_lock_;
lck_mtx_t *log_dataqueue_lock_;
IOSharedDataQueue *decision_dataqueue_;
IOSharedDataQueue *log_dataqueue_;
uint32_t failed_decision_queue_requests_;
uint32_t failed_log_queue_requests_;
int32_t listener_invocations_;
int32_t pid_monitor_thread_count_ = 0;
pid_t client_pid_;
kauth_listener_t vnode_listener_;
kauth_listener_t fileop_listener_;
struct timespec ts_ = {
.tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000};
};
/**
The kauth callback function for the Vnode scope
@param credential actor's credentials
@param idata data that was passed when the listener was registered
@param action action that was requested
@param arg0 VFS context
@param arg1 Vnode being operated on
@param arg2 Parent Vnode. May be nullptr.
@param arg3 Pointer to an errno-style error.
*/
extern "C" int vnode_scope_callback(kauth_cred_t credential, void *idata,
kauth_action_t action, uintptr_t arg0,
uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3);
/**
The kauth callback function for the FileOp scope
@param credential actor's credentials
@param idata data that was passed when the listener was registered
@param action action that was requested
@param arg0 depends on action, usually the vnode ref.
@param arg1 depends on action.
@param arg2 depends on action, usually 0.
@param arg3 depends on action, usually 0.
*/
extern "C" int fileop_scope_callback(kauth_cred_t credential, void *idata,
kauth_action_t action, uintptr_t arg0,
uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3);
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H

View File

@@ -1,55 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santa_driver/SantaDriver.h"
#define super IOService
#define SantaDriver com_google_SantaDriver
// The defines above can'be used in this function, we must use the full names.
OSDefineMetaClassAndStructors(com_google_SantaDriver, IOService);
bool SantaDriver::start(IOService *provider) {
if (!super::start(provider)) return false;
santaDecisionManager = new SantaDecisionManager;
if (!santaDecisionManager->init() ||
santaDecisionManager->StartListener() != kIOReturnSuccess) {
santaDecisionManager->release();
santaDecisionManager = nullptr;
return false;
}
registerService();
LOGI("Loaded, version %s.", OSKextGetCurrentVersionString());
return true;
}
void SantaDriver::stop(IOService *provider) {
santaDecisionManager->StopListener();
santaDecisionManager->release();
santaDecisionManager = nullptr;
LOGI("Unloaded.");
super::stop(provider);
}
SantaDecisionManager *SantaDriver::GetDecisionManager() const {
return santaDecisionManager;
}
#undef super

View File

@@ -1,46 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTA_DRIVER__SANTADRIVER_H
#define SANTA__SANTA_DRIVER__SANTADRIVER_H
#include <IOKit/IOService.h>
#include <libkern/OSKextLib.h>
#include "Source/common/SNTLogging.h"
#include "Source/santa_driver/SantaDecisionManager.h"
///
/// The driver class, which provides the start/stop functions and holds
/// the SantaDecisionManager instance which the connected client
/// communicates with.
///
class com_google_SantaDriver : public IOService {
OSDeclareDefaultStructors(com_google_SantaDriver);
public:
/// Called by the kernel when the kext is loaded
bool start(IOService *provider) override;
/// Called by the kernel when the kext is unloaded
void stop(IOService *provider) override;
/// Returns a pointer to the SantaDecisionManager created in start().
SantaDecisionManager *GetDecisionManager() const;
private:
SantaDecisionManager *santaDecisionManager;
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVER_H

View File

@@ -1,305 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santa_driver/SantaDriverClient.h"
#define super IOUserClient
#define SantaDriverClient com_google_SantaDriverClient
// The defines above can'be used in this function, must use the full names.
OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient);
#pragma mark Driver Management
bool SantaDriverClient::initWithTask(
task_t owningTask, void *securityID, UInt32 type) {
if (clientHasPrivilege(
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
LOGW("Unprivileged client attempted to connect.");
return false;
}
if (!super::initWithTask(owningTask, securityID, type)) return false;
return true;
}
bool SantaDriverClient::start(IOService *provider) {
myProvider = OSDynamicCast(com_google_SantaDriver, provider);
if (!myProvider) return false;
decisionManager = myProvider->GetDecisionManager();
if (!decisionManager) return false;
decisionManager->retain();
return super::start(provider);
}
void SantaDriverClient::stop(IOService *provider) {
myProvider = nullptr;
decisionManager->release();
decisionManager = nullptr;
super::stop(provider);
}
IOReturn SantaDriverClient::clientDied() {
LOGI("Client died.");
decisionManager->DisconnectClient(true);
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
}
IOReturn SantaDriverClient::clientClose() {
LOGI("Client disconnected.");
decisionManager->DisconnectClient();
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
}
bool SantaDriverClient::didTerminate(IOService *provider, IOOptionBits options, bool *defer) {
decisionManager->DisconnectClient(false, 0);
if (myProvider && myProvider->isOpen(this)) myProvider->close(this);
return super::didTerminate(provider, options, defer);
}
#pragma mark Fetching memory and data queue notifications
IOReturn SantaDriverClient::registerNotificationPort(
mach_port_t port, UInt32 type, UInt32 ref) {
if (port == MACH_PORT_NULL) return kIOReturnError;
switch (type) {
case QUEUETYPE_DECISION:
decisionManager->SetDecisionPort(port);
break;
case QUEUETYPE_LOG:
decisionManager->SetLogPort(port);
break;
default:
return kIOReturnBadArgument;
}
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clientMemoryForType(
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) {
switch (type) {
case QUEUETYPE_DECISION:
*options = 0;
*memory = decisionManager->GetDecisionMemoryDescriptor();
decisionManager->ConnectClient(proc_selfpid());
break;
case QUEUETYPE_LOG:
*options = 0;
*memory = decisionManager->GetLogMemoryDescriptor();
break;
default:
return kIOReturnBadArgument;
}
(*memory)->retain();
return kIOReturnSuccess;
}
#pragma mark Callable Methods
IOReturn SantaDriverClient::open(
OSObject *target,
void *reference,
IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (me->isInactive()) return kIOReturnNotAttached;
if (!me->myProvider->open(me)) {
LOGW("A second client tried to connect.");
return kIOReturnExclusiveAccess;
}
LOGI("Client connected.");
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::allow_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::allow_compiler(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW_COMPILER);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_DENY);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ACK);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const bool non_root_only = static_cast<const bool>(arguments->scalarInput[0]);
me->decisionManager->ClearCache(non_root_only);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::remove_cache_entry(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->RemoveFromCache(*vnode_id);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::cache_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
arguments->scalarOutput[0] = me->decisionManager->RootCacheCount();
arguments->scalarOutput[1] = me->decisionManager->NonRootCacheCount();
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::check_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(*vnode_id);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::cache_bucket_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
santa_bucket_count_t *counts = reinterpret_cast<santa_bucket_count_t *>(
arguments->structureOutput);
const santa_bucket_count_t *input = reinterpret_cast<const santa_bucket_count_t *>(
arguments->structureInput);
uint16_t s = sizeof(counts->per_bucket) / sizeof(uint16_t);
counts->start = input->start;
me->decisionManager->CacheBucketCount(counts->per_bucket, &s, &(counts->start));
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::filemod_prefix_filter_add(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const char *prefix = reinterpret_cast<const char *>(arguments->structureInput);
return me->decisionManager->FilemodPrefixFilterAdd(prefix, arguments->scalarOutput);
}
IOReturn SantaDriverClient::filemod_prefix_filter_reset(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
me->decisionManager->FilemodPrefixFilterReset();
return kIOReturnSuccess;
}
#pragma mark Method Resolution
IOReturn SantaDriverClient::externalMethod(
UInt32 selector,
IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch,
OSObject *target,
void *reference) {
/// Array of methods callable by clients. The order of these must match the
/// order of the items in SantaDriverMethods in SNTKernelCommon.h
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::allow_compiler, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::deny_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::acknowledge_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
{ &SantaDriverClient::remove_cache_entry, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
{ &SantaDriverClient::check_cache, 0, sizeof(santa_vnode_id_t), 1, 0 },
{ &SantaDriverClient::cache_bucket_count, 0, sizeof(santa_bucket_count_t),
0, sizeof(santa_bucket_count_t) },
{ &SantaDriverClient::filemod_prefix_filter_add, 0, sizeof(const char[MAXPATHLEN]), 1, 0 },
{ &SantaDriverClient::filemod_prefix_filter_reset, 0, 0, 0, 0 },
};
if (selector >= static_cast<UInt32>(kSantaUserClientNMethods)) {
return kIOReturnBadArgument;
}
dispatch = &(sMethods[selector]);
if (!target) target = this;
return super::externalMethod(selector, arguments, dispatch, target, reference);
}
#undef super

View File

@@ -1,136 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
#define SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
#include <IOKit/IOUserClient.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "Source/common/SNTKernelCommon.h"
#include "Source/santa_driver/SantaDecisionManager.h"
#include "Source/santa_driver/SantaDriver.h"
///
/// This class is instantiated by IOKit when a new client process attempts to
/// connect to the driver. Starting, stopping, handling connections, allocating
/// shared memory and establishing a data queue is handled here.
///
/// Documentation on how the IOUserClient parts of this code work can be found
/// here:
/// https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html
/// https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingDeviceDriver/WritingDeviceDriver.pdf
///
class com_google_SantaDriverClient : public IOUserClient {
OSDeclareDefaultStructors(com_google_SantaDriverClient);
public:
/// Called as part of IOServiceOpen in clients
bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;
/// Called after initWithTask as part of IOServiceOpen
bool start(IOService *provider) override;
/// Called when this class is stopping
void stop(IOService *provider) override;
/// Called when a client manually disconnects (via IOServiceClose)
IOReturn clientClose() override;
/// Called when a client dies
IOReturn clientDied() override;
/// Called during termination
bool didTerminate(IOService *provider, IOOptionBits options,
bool *defer) override;
/// Called in clients with IOConnectSetNotificationPort
IOReturn registerNotificationPort(mach_port_t port, UInt32 type,
UInt32 refCon) override;
/// Called in clients with IOConnectMapMemory
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options,
IOMemoryDescriptor **memory) override;
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
/// to the requested selector using the SantaDriverMethods enum in
/// SNTKernelCommon.
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch, OSObject *target,
void *reference) override;
///
/// The userspace callable methods are below. Each method corresponds
/// to an entry in SantaDriverMethods.
///
/// Called during client connection.
static IOReturn open(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a binary.
static IOReturn allow_binary(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a compiler binary.
static IOReturn allow_compiler(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to deny a binary.
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);
/// The daemon call this to remove a single cache entry.
static IOReturn remove_cache_entry(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in the cache
static IOReturn cache_count(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out the status of a vnode_id in the cache.
/// Output will be a santa_action_t.
static IOReturn check_cache(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in each cache
/// bucket. Input and output are both an instance of santa_bucket_count_t.
static IOReturn cache_bucket_count(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to add filemod prefix filters at daemon startup.
static IOReturn filemod_prefix_filter_add(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to reset the prefix filter tree.
static IOReturn filemod_prefix_filter_reset(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
private:
com_google_SantaDriver *myProvider;
SantaDecisionManager *decisionManager;
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H

View File

@@ -1,878 +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/Foundation.h>
#import <IOKit/IODataQueueClient.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/kext/KextManager.h>
#import <CommonCrypto/CommonDigest.h>
#include <libkern/OSKextLib.h>
#include <mach/mach.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <cmath>
#include <ctime>
#include <iostream>
#include <numeric>
#include <vector>
#include "Source/common/SNTKernelCommon.h"
///
/// Kernel Extension Tests
///
/// Build and launch as root. This target is dependent on the santa-driver target and these
/// tests will load santa-driver from the same location this binary is executed from, unloading
/// any existing driver (and daemon) if necessary.
///
#define TSTART(testName) \
do { \
printf(" %-50s ", testName); \
} while (0)
#define TPASS() \
do { \
printf("PASS\n"); \
} while (0)
#define TPASSINFO(fmt, ...) \
do { \
printf("PASS\n " fmt "\n", ##__VA_ARGS__); \
} while (0)
#define TFAIL() \
do { \
printf("FAIL\n"); \
[self unloadExtension]; \
exit(1); \
} while (0)
#define TFAILINFO(fmt, ...) \
do { \
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
[self unloadExtension]; \
exit(1); \
} while (0)
@interface SantaKernelTests : NSObject
@property io_connect_t connection;
// A block that tests can set to handle specific files/binaries.
// The block should return an action to respond to the kernel with.
// If no block is specified or no action is returned, the exec will be allowed.
@property(atomic, copy) santa_action_t (^handlerBlock)(santa_message_t msg);
- (void)unloadDaemon;
- (void)unloadExtension;
- (void)loadExtension;
- (void)runTests;
@end
@implementation SantaKernelTests
#pragma mark - Test Helpers
/// Return an initialized NSTask for |path| with stdout, stdin and stderr directed to /dev/null
- (NSTask *)taskWithPath:(NSString *)path {
NSTask *t = [[NSTask alloc] init];
t.launchPath = path;
t.standardInput = nil;
t.standardOutput = nil;
t.standardError = nil;
return t;
}
- (NSString *)sha256ForPath:(NSString *)path {
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
NSData *fData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:nil];
CC_SHA256([fData bytes], (unsigned int)[fData length], sha256);
char buf[CC_SHA256_DIGEST_LENGTH * 2 + 1];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
snprintf(buf + (2 * i), 4, "%02x", (unsigned char)sha256[i]);
}
buf[CC_SHA256_DIGEST_LENGTH * 2] = '\0';
return @(buf);
}
/// Return the path to the version of ld being used by clang.
- (NSString *)ldPath {
static NSString *path;
if (!path) {
NSTask *xcrun = [self taskWithPath:@"/usr/bin/xcrun"];
xcrun.arguments = @[ @"-f", @"ld" ];
xcrun.standardOutput = [NSPipe pipe];
@try {
[xcrun launch];
[xcrun waitUntilExit];
} @catch (NSException *exception) {
return nil;
}
if (xcrun.terminationStatus != 0) return nil;
NSData *data = [[xcrun.standardOutput fileHandleForReading] readDataToEndOfFile];
if (!data) return nil;
path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
return path;
}
#pragma mark - Driver Helpers
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
/// passing the |vnodeID|.
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeid {
switch (action) {
case ACTION_RESPOND_ALLOW:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_DENY:
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ACK:
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ALLOW_COMPILER:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
default:
TFAILINFO("postToKernelAction:forVnodeID: received unknown action type: %d", action);
break;
}
}
/// Call in-kernel function: |kSantaUserClientClearCache|
- (void)flushCache {
uint64_t nonRootOnly = 0;
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, &nonRootOnly, 1, 0, 0);
}
#pragma mark - Connection Tests
/// Tests the process of locating, attaching and opening the driver. Also verifies that the
/// driver correctly refuses non-privileged connections.
- (void)connectionTests {
kern_return_t kr;
io_service_t serviceObject;
CFDictionaryRef classToMatch;
TSTART("Creates matching service dictionary");
if (!(classToMatch = IOServiceMatching(USERCLIENT_CLASS))) {
TFAIL();
}
TPASS();
TSTART("Locates Santa driver");
serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch);
if (!serviceObject) {
TFAILINFO("Is santa-driver.kext loaded?");
}
TPASS();
TSTART("Driver refuses non-privileged connections");
(void)setegid(-2);
(void)seteuid(-2);
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
if (kr != kIOReturnBadArgument) {
TFAIL();
}
(void)setegid(0);
(void)seteuid(0);
TPASS();
TSTART("Attaches to and starts Santa service");
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
IOObjectRelease(serviceObject);
if (kr != kIOReturnSuccess) {
TFAILINFO("KR: %d", kr);
}
TPASS();
TSTART("Calls 'open' method on driver");
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr == kIOReturnExclusiveAccess) {
TFAILINFO("A client is already connected to the driver.\n"
"Please kill the existing client and re-run the test.");
} else if (kr != kIOReturnSuccess) {
TFAILINFO("KR: %d", kr);
}
TPASS();
TSTART("Refuses second client");
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr != kIOReturnExclusiveAccess) {
TFAIL();
}
TPASS();
}
#pragma mark - Listener
/// Tests the process of allocating & registering a notification port and mapping shared memory.
/// From then on, monitors the IODataQueue and responds for files specifically used in other tests.
/// For everything else, allows execution normally to avoid deadlocking the system.
- (void)beginListening {
TSTART("Allocates a notification port");
mach_port_t receivePort;
if (!(receivePort = IODataQueueAllocateNotificationPort())) {
TFAIL();
}
TPASS();
TSTART("Registers the notification port");
kern_return_t kr =
IOConnectSetNotificationPort(self.connection, QUEUETYPE_DECISION, receivePort, 0);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), receivePort);
TFAILINFO("KR: %d", kr);
return;
}
TPASS();
TSTART("Maps shared memory");
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(), &address, &size,
kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), receivePort);
TFAILINFO("KR: %d", kr);
}
TPASS();
/// Begin listening for events
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
do {
while (IODataQueueDataAvailable(queueMemory)) {
santa_message_t vdata;
UInt32 dataSize = sizeof(vdata);
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
if (kr != kIOReturnSuccess) {
TFAILINFO("Error receiving data: %d", kr);
continue;
}
if (vdata.action != ACTION_REQUEST_BINARY) continue;
santa_action_t action = ACTION_RESPOND_ALLOW;
@synchronized(self) {
if (self.handlerBlock) action = self.handlerBlock(vdata);
}
[self postToKernelAction:action forVnodeID:vdata.vnode_id];
}
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
IOConnectUnmapMemory(self.connection, kIODefaultMemoryType, mach_task_self(), address);
mach_port_destroy(mach_task_self(), receivePort);
}
#pragma mark - Functional Tests
- (void)receiveAndBlockTests {
TSTART("Blocks denied binaries");
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/ed", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
return ACTION_RESPOND_ALLOW;
};
NSTask *ed = [self taskWithPath:@"/bin/ed"];
@try {
[ed launch];
[ed waitUntilExit];
TFAIL();
} @catch (NSException *exception) {
TPASS();
}
}
- (void)receiveAndCacheTests {
TSTART("Permits & caches allowed binaries");
__block int timesSeenLs = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/ls", msg.path, 7) == 0) ++timesSeenLs;
return ACTION_RESPOND_ALLOW;
};
NSTask *ls = [self taskWithPath:@"/bin/ls"];
[ls launch];
[ls waitUntilExit];
if (timesSeenLs != 1) {
TFAILINFO("Didn't record first run of ls");
}
ls = [self taskWithPath:@"/bin/ls"];
[ls launch];
[ls waitUntilExit];
if (timesSeenLs > 1) {
TFAILINFO("Received request for ls a second time");
}
TPASS();
}
- (void)invalidatesCacheTests {
TSTART("Invalidates cache for manually closed FDs");
NSFileManager *fm = [NSFileManager defaultManager];
NSString *target =
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
[fm removeItemAtPath:target error:nil];
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Copy the pwd binary to a new file
if (![fm copyItemAtPath:@"/bin/pwd" toPath:target error:nil]) {
TFAILINFO("Failed to create temp file");
}
// Launch the new file to put it in the cache
NSTask *pwd = [self taskWithPath:target];
[pwd launch];
[pwd waitUntilExit];
// Exit if this fails with a useful message.
if ([pwd terminationStatus] != 0) {
TFAILINFO("First launch of test binary failed");
}
// Now replace the contents of the test file (which is cached) with the contents of /bin/ed,
// which is blocked by SHA-256 during the tests.
FILE *infile = fopen("/bin/ed", "r");
FILE *outfile = fopen(target.UTF8String, "w");
int ch;
while ((ch = fgetc(infile)) != EOF) {
fputc(ch, outfile);
}
fclose(infile);
// Now try running the temp file again. If it succeeds, the test failed.
NSTask *ed = [self taskWithPath:target];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after write while file open");
[fm removeItemAtPath:target error:nil];
} @catch (NSException *exception) {
// This is a pass, but we have more to do.
}
// Close the file to flush the write.
fclose(outfile);
// And try running the temp file again. If it succeeds, the test failed.
ed = [self taskWithPath:target];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after file closed");
} @catch (NSException *exception) {
TPASS();
} @finally {
[fm removeItemAtPath:target error:nil];
}
}
- (void)invalidatesCacheAutoCloseTest {
TSTART("Invalidates cache for auto closed FDs");
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Create temporary file
NSFileManager *fm = [NSFileManager defaultManager];
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
TFAILINFO("Failed to create temp file");
}
// Launch the new file to put it in the cache
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
[pwd launch];
[pwd waitUntilExit];
if ([pwd terminationStatus] != 0) {
TFAILINFO("Second launch of test binary failed");
}
// Replace file contents using dd, which doesn't close FDs
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
NSTask *dd = [self taskWithPath:@"/bin/dd"];
dd.arguments = @[
@"if=/bin/ed", @"of=invalidacachetest_tmp", @"bs=1",
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
];
[dd launch];
[dd waitUntilExit];
// And try running the temp file again. If it succeeds, the test failed.
NSTask *ed = [self taskWithPath:@"invalidacachetest_tmp"];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after file closed");
} @catch (NSException *exception) {
TPASS();
} @finally {
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
}
}
- (void)clearCacheTests {
TSTART("Can clear cache");
__block int timesSeenCat = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/cat", msg.path, 8) == 0) ++timesSeenCat;
return ACTION_RESPOND_ALLOW;
};
NSTask *cat = [self taskWithPath:@"/bin/cat"];
[cat launch];
[cat waitUntilExit];
if (timesSeenCat != 1) {
TFAILINFO("Didn't record first run of cat");
}
[self flushCache];
cat = [self taskWithPath:@"/bin/cat"];
[cat launch];
[cat waitUntilExit];
if (timesSeenCat != 2) {
TFAIL();
}
TPASS();
}
- (void)blocksDeniedTracedBinaries {
TSTART("Denies blocked processes running while traced");
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/mv", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
return ACTION_RESPOND_ALLOW;
};
pid_t pid = fork();
if (pid < 0) {
TFAILINFO("Failed to fork");
} else if (pid > 0) {
int status;
while (waitpid(pid, &status, 0) != pid)
; // handle EINTR
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
TPASS();
} else if (WIFSTOPPED(status)) {
TFAILINFO("Process was executed and is waiting for debugger");
} else {
TFAILINFO("Process did not exit with EPERM as expected");
}
} else if (pid == 0) {
fclose(stdout);
fclose(stderr);
ptrace(PT_TRACE_ME, 0, 0, 0);
execl("/bin/mv", "mv", NULL);
_exit(errno);
}
}
- (void)testCachePerformance {
TSTART("Test cache performance...");
// Execute echo 100 times, saving the time taken for each run
std::vector<double> times;
for (int i = 0; i < 100; ++i) {
NSTask *t = [[NSTask alloc] init];
t.launchPath = @"/bin/echo";
t.standardOutput = [NSPipe pipe];
auto start = std::chrono::steady_clock::now();
[t launch];
[t waitUntilExit];
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
if (i > 5) times.push_back(duration);
}
// Sort and remove first 10 and last 10 entries.
std::sort(times.begin(), times.end());
times.erase(times.begin(), times.begin() + 10);
times.erase(times.end() - 10, times.end());
// Calculate mean
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
// Calculate stdev
double accum = 0.0;
std::for_each(times.begin(), times.end(),
[&](const double d) { accum += (d - mean) * (d - mean); });
double stdev = sqrt(accum / (times.size() - 1));
if (mean > 80 || stdev > 10) {
TFAILINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
} else {
TPASSINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
}
}
- (void)testLargeBinary {
TSTART("Handles large binary...");
__block int calCount = 0;
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if (strncmp("/usr/bin/cal", msg.path, 12) == 0) {
if (calCount++) TFAILINFO("Large binary should not re-request");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
dispatch_get_global_queue(0, 0), ^{
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
});
return ACTION_RESPOND_ACK;
}
return ACTION_RESPOND_ALLOW;
};
@try {
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
[testexec launch];
int sleepCount = 0;
while ([testexec isRunning]) {
sleep(1);
if (++sleepCount > 5) TFAILINFO("Took longer than expected to start/stop");
}
} @catch (NSException *e) {
TFAILINFO("Failed to launch");
}
TPASS();
}
- (void)testPendingTransitiveRules {
TSTART("Adds pending transitive whitelist rules");
NSString *ldPath = [self ldPath];
if (!ldPath) {
TFAILINFO("Couldn't get path to ld");
}
// Clear out cached decisions from any previous tests.
[self flushCache];
__block int ldCount = 0;
__block int helloCount = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (!strcmp(ldPath.UTF8String, msg.path)) {
ldCount++;
return ACTION_RESPOND_ALLOW_COMPILER;
} else if (!strcmp("/private/tmp/hello", msg.path)) {
helloCount++;
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Write source file to /tmp/hello.c
FILE *out = fopen("/tmp/hello.c", "wb");
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
fclose(out);
// Then compile it with clang and ld, the latter of which has been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
[clang launch];
[clang waitUntilExit];
// Make sure that our version of ld marked as compiler was run. This assumes that
// "xcode-select -p" returns "/Applications/Xcode.app/Contents/Developer"
if (ldCount != 1) {
TFAILINFO("Didn't record run of ld");
}
// Check if we can now run /private/tmp/hello. If working correctly, there will already be
// a pending transitive rule in the cache, so no decision request will be sent to listener.
// If for some reason a decision request is sent, then binary will be denied.
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
@try {
[hello launch];
[hello waitUntilExit];
} @catch (NSException *exception) {
TFAILINFO("could not launch /private/tmp/hello: %s", exception.reason.UTF8String);
}
// Check that the listener was not consulted for the decision.
if (helloCount > 0) {
TFAILINFO("pending decision for /private/tmp/hello was not in cache");
}
// Clean up
remove("/tmp/hello");
remove("/tmp/hello.c");
TPASS();
}
- (void)testNoTransitiveRules {
TSTART("No transitive rule generated by non-compiler");
NSString *ldPath = [self ldPath];
if (!ldPath) {
TFAILINFO("Couldn't get path to ld");
}
// Clear out cached decisions from any previous tests.
[self flushCache];
__block int ldCount = 0;
__block int helloCount = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (!strcmp(ldPath.UTF8String, msg.path)) {
ldCount++;
return ACTION_RESPOND_ALLOW;
} else if (!strcmp("/private/tmp/hello", msg.path)) {
helloCount++;
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Write source file to /tmp/hello.c
FILE *out = fopen("/tmp/hello.c", "wb");
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
fclose(out);
// Then compile it with clang and ld, neither of which have been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
@try {
[clang launch];
[clang waitUntilExit];
} @catch (NSException *exception) {
TFAILINFO("Couldn't launch clang");
}
// Make sure that our version of ld was run. This assumes that "xcode-select -p"
// returns "/Applications/Xcode.app/Contents/Developer"
if (ldCount != 1) {
TFAILINFO("Didn't record run of ld");
}
// Check that we cannot run /private/tmp/hello.
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
@try {
[hello launch];
[hello waitUntilExit];
TFAILINFO("Should not have been able to launch /private/tmp/hello");
} @catch (NSException *exception) {
// All good
}
// Check that there wasn't a decision for /private/tmp/hello in the cache.
if (helloCount != 1) {
TFAILINFO("decision for /private/tmp/hello found in cache");
}
// Clean up
remove("/tmp/hello");
remove("/tmp/hello.c");
TPASS();
}
- (void)testFilemodPrefixFilter {
TSTART("Testing filemod prefix filter");
NSString *filter =
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
@"are the same for all non-accelerating observers, and he showed that the speed of light "
@"within a vacuum is the same no matter the speed at which an observer travels.";
// Create a buffer that has 1024 bytes of characters, non-terminating.
char buffer[MAXPATHLEN + 1]; // +1 is for the null byte from strlcpy(). It falls off the ledge.
for (int i = 0; i < 4; ++i) {
if (![filter getFileSystemRepresentation:buffer + (i * filter.length) maxLength:MAXPATHLEN]) {
TFAILINFO("Invalid filemod prefix filter: %s", filter.UTF8String);
}
}
strlcpy(&buffer[1016], "E = mc²", 9); // Fill in the last 8 bytes.
uint64_t n = 0;
uint32_t n_len = 1;
// The filter should currently be empty. It is reset when the client disconnects.
// Fill up the 1024 node capacity.
kern_return_t ret =
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess || n != 1024) {
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
}
// Make sure it will enforce capacity.
const char *too_much = "B";
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
too_much, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnNoResources || n != 1024) {
TFAILINFO("Failed enforce capacity: got %llu nodes expected 1024", n);
}
// Make sure it will prune.
const char *ignore_it_all = "A";
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
ignore_it_all, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
// Expect 1 "A" node.
if (ret != kIOReturnSuccess || n != 1) {
TFAILINFO("Failed to prune the prefix filter: got %llu nodes expected 1", n);
}
// Reset.
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
NULL, NULL);
// And fill it back up again.
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
buffer, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess || n != 1024) {
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
}
TPASS();
}
#pragma mark - Main
- (void)unloadDaemon {
NSTask *t = [[NSTask alloc] init];
t.launchPath = @"/bin/launchctl";
t.arguments = @[ @"remove", @"com.google.santad" ];
t.standardOutput = t.standardError = [NSPipe pipe];
[t launch];
[t waitUntilExit];
}
- (void)unloadExtension {
// Don't check the status of this, the kext may not be loaded..
OSStatus ret = KextManagerUnloadKextWithIdentifier(CFSTR("com.google.santa-driver"));
if (ret != kOSReturnSuccess && ret != kOSKextReturnNotFound) {
NSLog(@"Failed to unload extension: 0x%X", ret);
}
}
- (void)loadExtension {
TSTART("Loads extension");
NSError *error;
NSFileManager *fm = [NSFileManager defaultManager];
NSString *src = [[fm currentDirectoryPath] stringByAppendingPathComponent:@"santa-driver.kext"];
NSString *dest = [NSTemporaryDirectory() stringByAppendingPathComponent:@"santa-driver.kext"];
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
if (![fm copyItemAtPath:src toPath:dest error:&error] || error) {
TFAILINFO("Failed to copy kext: %s", error.description.UTF8String);
}
NSDictionary *attrs = @{
NSFileOwnerAccountName : @"root",
NSFileGroupOwnerAccountName : @"wheel",
NSFilePosixPermissions : @0755
};
[fm setAttributes:attrs ofItemAtPath:dest error:NULL];
for (NSString *path in [fm enumeratorAtPath:dest]) {
[fm setAttributes:attrs ofItemAtPath:[dest stringByAppendingPathComponent:path] error:NULL];
}
NSURL *destURL = [NSURL fileURLWithPath:dest];
OSStatus ret = KextManagerLoadKextWithURL((__bridge CFURLRef)destURL, NULL);
if (ret != kOSReturnSuccess) {
TFAILINFO("Failed to load kext: 0x%X", ret);
}
usleep(50000);
TPASS();
}
- (void)runTests {
printf("-> Connection tests:\n");
// Test that connection can be established
[self connectionTests];
// Open driver and begin listening for events. Run this on background thread
// so we can continue running tests.
[self performSelectorInBackground:@selector(beginListening) withObject:nil];
// Wait for driver to finish getting ready
sleep(1);
printf("\n-> Functional tests:\n");
[self receiveAndBlockTests];
[self receiveAndCacheTests];
[self invalidatesCacheTests];
[self invalidatesCacheAutoCloseTest];
[self clearCacheTests];
[self blocksDeniedTracedBinaries];
[self testLargeBinary];
[self testPendingTransitiveRules];
[self testNoTransitiveRules];
[self testFilemodPrefixFilter];
printf("\n-> Performance tests:\n");
[self testCachePerformance];
printf("\nAll tests passed.\n\n");
}
@end
int main(int argc, const char *argv[]) {
@autoreleasepool {
setbuf(stdout, NULL);
if (getuid() != 0) {
printf("Please run as root\n");
exit(1);
}
SantaKernelTests *skt = [[SantaKernelTests alloc] init];
printf("\nSanta Kernel Tests\n==================\n\n");
printf("-> Loading tests:\n");
[skt unloadDaemon];
[skt unloadExtension];
[skt loadExtension];
printf("\n");
[skt runTests];
[skt unloadExtension];
}
return 0;
}

View File

@@ -1,29 +0,0 @@
#include <mach/mach_types.h>
#define STR_EXPAND(tok) #tok
#define STR(tok) STR_EXPAND(tok)
extern "C" {
extern kern_return_t _start(kmod_info_t *ki, void *data);
extern kern_return_t _stop(kmod_info_t *ki, void *data);
__attribute__((visibility("default"))) \
KMOD_EXPLICIT_DECL(com.google.santa-driver, STR(SANTA_VERSION), _start, _stop)
__private_extern__ kmod_start_func_t *_realmain = 0;
__private_extern__ kmod_stop_func_t *_antimain = 0;
__private_extern__ int _kext_apple_cc = __APPLE_CC__ ;
}
#include <IOKit/IOService.h>
#include <IOKit/IOUserClient.h>
// The macOS 10.15 SDK added these Dispatch methods but they aren't
// available on older macOS versions and so prevent kext linking.
// Defining them here as hidden weak no-op's fixes linking and seems to work.
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSMetaClassBase::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSObject::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOService::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOUserClient::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }

View File

@@ -2,6 +2,10 @@ load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_applicatio
licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
)
objc_library(
name = "santabs_lib",
srcs = [
@@ -12,6 +16,7 @@ objc_library(
deps = [
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCNotifierInterface",
"@FMDB",
@@ -23,8 +28,17 @@ objc_library(
macos_command_line_application(
name = "santabundleservice",
bundle_id = "com.google.santa.bundleservice",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "//profiles:santa_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santabs_lib"],

View File

@@ -3,6 +3,10 @@ load("//:helper.bzl", "santa_unit_test")
licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
)
objc_library(
name = "santactl_lib",
srcs = [
@@ -15,25 +19,9 @@ objc_library(
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandVersion.m",
"Commands/sync/NSData+Zlib.h",
"Commands/sync/NSData+Zlib.m",
"Commands/sync/SNTCommandSync.m",
"Commands/sync/SNTCommandSyncConstants.h",
"Commands/sync/SNTCommandSyncConstants.m",
"Commands/sync/SNTCommandSyncEventUpload.h",
"Commands/sync/SNTCommandSyncEventUpload.m",
"Commands/sync/SNTCommandSyncManager.h",
"Commands/sync/SNTCommandSyncManager.m",
"Commands/sync/SNTCommandSyncPostflight.h",
"Commands/sync/SNTCommandSyncPostflight.m",
"Commands/sync/SNTCommandSyncPreflight.h",
"Commands/sync/SNTCommandSyncPreflight.m",
"Commands/sync/SNTCommandSyncRuleDownload.h",
"Commands/sync/SNTCommandSyncRuleDownload.m",
"Commands/sync/SNTCommandSyncStage.h",
"Commands/sync/SNTCommandSyncStage.m",
"Commands/sync/SNTCommandSyncState.h",
"Commands/sync/SNTCommandSyncState.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetrics.m",
"Commands/SNTCommandSync.m",
] + select({
"//:opt_build": [],
"//conditions:default": [
@@ -46,51 +34,45 @@ objc_library(
sdk_dylibs = ["libz"],
sdk_frameworks = ["IOKit"],
deps = [
":FCM_lib",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"//Source/santasyncservice:sync_lib",
"@FMDB",
"@MOLAuthenticatingURLSession",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
objc_library(
name = "FCM_lib",
srcs = ["Commands/sync/SNTCommandSyncFCM.m"],
hdrs = ["Commands/sync/SNTCommandSyncFCM.h"],
sdk_frameworks = ["SystemConfiguration"],
deps = [
"@MOLAuthenticatingURLSession",
],
)
macos_command_line_application(
name = "santactl",
bundle_id = "com.google.santa.ctl",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "//profiles:santa_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santactl_lib"],
)
@@ -108,6 +90,7 @@ santa_unit_test(
"//Source/common:SNTCachedDecision",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTXPCControlInterface",
"@MOLCertificate",
"@MOLCodesignChecker",
@@ -117,46 +100,32 @@ santa_unit_test(
)
santa_unit_test(
name = "SNTCommandSyncTest",
name = "SNTCommandMetricsTest",
srcs = [
"Commands/sync/NSData+Zlib.h",
"Commands/sync/NSData+Zlib.m",
"Commands/sync/SNTCommandSync.m",
"Commands/sync/SNTCommandSyncConstants.h",
"Commands/sync/SNTCommandSyncConstants.m",
"Commands/sync/SNTCommandSyncEventUpload.h",
"Commands/sync/SNTCommandSyncEventUpload.m",
"Commands/sync/SNTCommandSyncManager.h",
"Commands/sync/SNTCommandSyncManager.m",
"Commands/sync/SNTCommandSyncPostflight.h",
"Commands/sync/SNTCommandSyncPostflight.m",
"Commands/sync/SNTCommandSyncPreflight.h",
"Commands/sync/SNTCommandSyncPreflight.m",
"Commands/sync/SNTCommandSyncRuleDownload.h",
"Commands/sync/SNTCommandSyncRuleDownload.m",
"Commands/sync/SNTCommandSyncStage.h",
"Commands/sync/SNTCommandSyncStage.m",
"Commands/sync/SNTCommandSyncState.h",
"Commands/sync/SNTCommandSyncState.m",
"SNTCommand.h",
"SNTCommand.m",
"SNTCommandController.h",
"SNTCommandController.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetricsTest.m",
],
sdk_dylibs = ["libz"],
structured_resources = glob(["Commands/testdata/*"]),
visibility = ["//:santa_package_group"],
deps = [
":FCM_lib",
":santactl_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTMetricSet",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLXPCConnection",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
"@OCMock",
"@MOLXPCConnection",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTCommandFileInfoTest",
":SNTCommandMetricsTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -41,6 +41,7 @@ static NSString *const kCodeSigned = @"Code-signed";
static NSString *const kRule = @"Rule";
static NSString *const kSigningChain = @"Signing Chain";
static NSString *const kUniversalSigningChain = @"Universal Signing Chain";
static NSString *const kTeamID = @"Team ID";
// signing chain keys
static NSString *const kCommonName = @"Common Name";
@@ -70,7 +71,7 @@ NSString *formattedStringForKeyArray(NSArray<NSString *> *array) {
// Properties set from commandline flags
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) int certIndex; // 0 means no cert-index specified
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic, copy) NSDictionary<NSString *, NSRegularExpression *> *outputFilters;
@@ -109,6 +110,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadURL;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadTimestamp;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadAgent;
@property(readonly, copy, nonatomic) SNTAttributeBlock teamID;
@property(readonly, copy, nonatomic) SNTAttributeBlock type;
@property(readonly, copy, nonatomic) SNTAttributeBlock pageZero;
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
@@ -161,9 +163,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
@"%@\n"
@" --cert-index: Supply an integer corresponding to a certificate of the\n"
@" signing chain to show info only for that certificate.\n"
@" 1 for the leaf certificate\n"
@" -1 for the root certificate\n"
@" 2 and up for the intermediates / root\n"
@" 0 up to n for the leaf certificate up to the root\n"
@" -1 down to -n-1 for the root certificate down to the leaf\n"
@"\n"
@" --filter: Use predicates of the form 'key=regex' to filter out which files\n"
@" are displayed. Valid keys are the same as for --key. Value is a\n"
@@ -183,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,
kDownloadURL, kDownloadTimestamp, kDownloadAgent, kTeamID, kType, kPageZero, kCodeSigned, kRule,
kSigningChain, kUniversalSigningChain
];
}
@@ -215,7 +216,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
kCodeSigned : self.codeSigned,
kRule : self.rule,
kSigningChain : self.signingChain,
kUniversalSigningChain : self.universalSigningChain
kUniversalSigningChain : self.universalSigningChain,
kTeamID : self.teamID,
};
_printQueue = dispatch_queue_create("com.google.santactl.print_queue", DISPATCH_QUEUE_SERIAL);
@@ -355,13 +357,15 @@ REGISTER_COMMAND_NAME(@"fileinfo")
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSError *err;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&err];
[[cmd.daemonConn remoteObjectProxy] decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
[[cmd.daemonConn remoteObjectProxy]
decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
teamID:[csc.signingInformation valueForKey:@"teamid"]
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
cmd.daemonUnavailable = YES;
return kCommunicationErrorMsg;
@@ -375,10 +379,13 @@ REGISTER_COMMAND_NAME(@"fileinfo")
case SNTEventStateBlockBinary: [output appendString:@" (Binary)"]; break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate: [output appendString:@" (Certificate)"]; break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID: [output appendString:@" (TeamID)"]; break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
case SNTEventStateAllowCompiler: [output appendString:@" (Compiler)"]; break;
case SNTEventStateAllowTransitive: [output appendString:@" (Transitive)"]; break;
default: output = @"None".mutableCopy; break;
}
if (cmd.prettyOutput) {
@@ -458,6 +465,13 @@ REGISTER_COMMAND_NAME(@"fileinfo")
};
}
- (SNTAttributeBlock)teamID {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
return [csc.signingInformation valueForKey:@"teamid"];
};
}
#pragma mark -
// Entry point for the command.
@@ -575,12 +589,24 @@ REGISTER_COMMAND_NAME(@"fileinfo")
// First build up a dictionary containing all the information we want to print out
NSMutableDictionary *outputDict = [NSMutableDictionary dictionary];
if (self.certIndex) {
int index = [self.certIndex intValue];
// --cert-index flag implicitly means that we want only the signing chain. So we find the
// specified certificate in the signing chain, then print out values for all keys in cert.
NSArray *signingChain = self.propertyMap[kSigningChain](self, fileInfo);
if (!signingChain || !signingChain.count) return; // check signing chain isn't empty
int index = (self.certIndex == -1) ? (int)signingChain.count - 1 : self.certIndex - 1;
if (index < 0 || index >= (int)signingChain.count) return; // check that index is valid
if (index < 0) {
index = (int)signingChain.count - -(index);
if (index < 0) {
fprintf(stderr, "Invalid --cert-index: %d\n", index);
return;
}
} else {
if (index >= (int)signingChain.count) {
fprintf(stderr, "Invalid --cert-index: %d\n", index);
return;
}
}
NSDictionary *cert = signingChain[index];
// Check if we should skip over this item based on outputFilters.
@@ -687,14 +713,12 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
int index = 0;
NSScanner *scanner = [NSScanner scannerWithString:arguments[i]];
if (![scanner scanInt:&index] || !scanner.atEnd || index == 0 || index < -1) {
[self
printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid argument for --cert-index\n"
@" --cert-index argument must be one of -1, 1, 2, 3, ...",
arguments[i]]];
if (![scanner scanInt:&index] || !scanner.atEnd) {
[self printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid argument for --cert-index\n",
arguments[i]]];
}
self.certIndex = index;
self.certIndex = @(index);
} else if ([arg caseInsensitiveCompare:@"--key"] == NSOrderedSame) {
i += 1; // advance to next argument and grab the key
if (i >= nargs || [arguments[i] hasPrefix:@"--"]) {

View File

@@ -24,7 +24,7 @@
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) int certIndex;
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
+ (NSArray *)fileInfoKeys;
@@ -71,7 +71,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
- (void)testParseArgumentsCertIndex {
NSArray *filePaths = [self.cfi parseArguments:@[ @"--cert-index", @"1", @"/usr/bin/yes" ]];
XCTAssertEqual(self.cfi.certIndex, 1);
XCTAssertEqual([self.cfi.certIndex intValue], 1);
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
}

View File

@@ -0,0 +1,23 @@
/// Copyright 2021 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/Foundation.h>
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@interface SNTCommandMetrics : SNTCommand <SNTCommandProtocol>
- (void)prettyPrintMetrics:(NSDictionary *)metircs asJSON:(BOOL)exportJSON;
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args;
@end

View File

@@ -0,0 +1,168 @@
/// Copyright 2021 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/Foundation.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/SNTCommandMetrics.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@implementation SNTCommandMetrics
REGISTER_COMMAND_NAME(@"metrics")
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
}
+ (NSString *)shortHelpText {
return @"Show Santa metric information.";
}
+ (NSString *)longHelpText {
return (@"Provides metrics about Santa's operation while it's running.\n"
@"Pass prefixes to filter list of metrics, if desired.\n"
@" Use --json to output in JSON format");
}
- (void)prettyPrintRootLabels:(NSDictionary *)rootLabels {
for (NSString *label in rootLabels) {
const char *labelStr = [label cStringUsingEncoding:NSUTF8StringEncoding];
const char *valueStr = [rootLabels[label] cStringUsingEncoding:NSUTF8StringEncoding];
printf(" %-25s | %s\n", labelStr, valueStr);
}
}
- (void)prettyPrintMetricValues:(NSDictionary *)metrics {
for (NSString *metricName in metrics) {
NSDictionary *metric = metrics[metricName];
const char *metricNameStr = [metricName UTF8String];
const char *description = [metric[@"description"] UTF8String];
NSString *metricType = SNTMetricMakeStringFromMetricType([metric[@"type"] integerValue]);
const char *metricTypeStr = [metricType UTF8String];
printf(" %-25s | %s\n", "Metric Name", metricNameStr);
printf(" %-25s | %s\n", "Description", description);
printf(" %-25s | %s\n", "Type", metricTypeStr);
for (NSString *fieldName in metric[@"fields"]) {
for (NSDictionary *field in metric[@"fields"][fieldName]) {
const char *fieldNameStr = [fieldName cStringUsingEncoding:NSUTF8StringEncoding];
const char *fieldValueStr = [field[@"value"] cStringUsingEncoding:NSUTF8StringEncoding];
const char *createdStr = [field[@"created"] UTF8String];
const char *lastUpdatedStr = [field[@"last_updated"] UTF8String];
const char *data = [[NSString stringWithFormat:@"%@", field[@"data"]] UTF8String];
if (strlen(fieldNameStr) > 0) {
printf(" %-25s | %s=%s\n", "Field", fieldNameStr, fieldValueStr);
}
printf(" %-25s | %s\n", "Created", createdStr);
printf(" %-25s | %s\n", "Last Updated", lastUpdatedStr);
printf(" %-25s | %s\n", "Data", data);
}
}
printf("\n");
}
}
- (void)prettyPrintMetrics:(NSDictionary *)metrics asJSON:(BOOL)exportJSON {
BOOL exportMetrics = [[SNTConfigurator configurator] exportMetrics];
NSURL *metricsURLStr = [[SNTConfigurator configurator] metricURL];
SNTMetricFormatType metricFormat = [[SNTConfigurator configurator] metricFormat];
NSUInteger metricExportInterval = [[SNTConfigurator configurator] metricExportInterval];
NSDictionary *normalizedMetrics = SNTMetricConvertDatesToISO8601Strings(metrics);
if (exportJSON) {
// Format
NSData *metricData = [NSJSONSerialization dataWithJSONObject:normalizedMetrics
options:NSJSONWritingPrettyPrinted
error:nil];
NSString *metricStr = [[NSString alloc] initWithData:metricData encoding:NSUTF8StringEncoding];
printf("%s\n", [metricStr UTF8String]);
return;
}
if (!exportMetrics) {
printf("Metrics not configured\n");
return;
}
printf(">>> Metrics Info\n");
printf(" %-25s | %s\n", "Metrics Server", [metricsURLStr.absoluteString UTF8String]);
printf(" %-25s | %s\n", "Metrics Format",
[SNTMetricStringFromMetricFormatType(metricFormat) UTF8String]);
printf(" %-25s | %lu\n", "Export Interval (seconds)", metricExportInterval);
printf("\n");
printf(">>> Root Labels\n");
[self prettyPrintRootLabels:normalizedMetrics[@"root_labels"]];
printf("\n");
printf(">>> Metrics \n");
[self prettyPrintMetricValues:normalizedMetrics[@"metrics"]];
}
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args {
NSMutableDictionary *outer = [metrics mutableCopy];
NSMutableDictionary *inner = [NSMutableDictionary dictionary];
__block BOOL hadFilter = NO;
[metrics[@"metrics"] enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
for (NSString *arg in args) {
if ([arg hasPrefix:@"-"]) continue;
hadFilter = YES;
if ([key hasPrefix:arg]) {
inner[key] = value;
}
}
}];
outer[@"metrics"] = inner;
return hadFilter ? outer : metrics;
}
- (void)runWithArguments:(NSArray *)arguments {
__block NSDictionary *metrics;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] metrics:^(NSDictionary *exportedMetrics) {
metrics = exportedMetrics;
dispatch_group_leave(group);
}];
// Wait a maximum of 5s for metrics collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
}
metrics = [self filterMetrics:metrics withArguments:arguments];
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];
exit(0);
}
@end

View File

@@ -0,0 +1,156 @@
/// Copyright 2021 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 <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/santactl/Commands/SNTCommandMetrics.h"
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
@interface SNTCommandMetricsTest : XCTestCase
@property NSString *tempDir;
@property id mockConfigurator;
@end
@implementation SNTCommandMetricsTest
- (void)setUp {
// create a temp dir
char template[] = "/tmp/sntcommandmetrictest.XXXXXXX";
char *tempPath = mkdtemp(template);
if (tempPath == NULL) {
NSLog(@"Unable to make temp directory");
exit(1);
}
self.tempDir =
[[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempPath
length:strlen(tempPath)];
// mock the SNTConfigurator
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator exportMetrics]).andReturn(YES);
OCMStub([self.mockConfigurator metricFormat]).andReturn(SNTMetricFormatTypeMonarchJSON);
OCMStub([self.mockConfigurator metricURL])
.andReturn([NSURL URLWithString:@"http://localhost:2444/submit"]);
OCMStub([self.mockConfigurator metricExportInterval]).andReturn((NSUInteger)30);
}
- (void)tearDown {
// delete the temp dir
NSError *err;
[[NSFileManager defaultManager] removeItemAtPath:self.tempDir error:&err];
if (err != nil) {
NSLog(@"unable to remove %@, error: %@", self.tempDir, err);
}
dup2(1, STDOUT_FILENO);
}
- (void)testPrettyPrintingJSON {
NSError *err;
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"Commands/testdata/metrics-prettyprint.json"];
NSString *goldenFileContents = [[NSString alloc]
initWithData:[NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&err]
encoding:NSUTF8StringEncoding];
XCTAssertNil(err, @"failed to read golden file %@ for testPrettyPrintingJSON", path);
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSString *outputPath = [NSString pathWithComponents:@[ self.tempDir, @"test.data" ]];
// redirect stdout
int fd = open([outputPath UTF8String], O_TRUNC | O_WRONLY | O_CREAT, 0600);
int saved_stdout = dup(fileno(stdout));
dup2(fd, fileno(stdout));
[metricsCmd prettyPrintMetrics:[SNTMetricFormatTestHelper createValidMetricsDictionary]
asJSON:YES];
// restore stdout
fflush(stdout);
dup2(saved_stdout, fileno(stdout));
// open test file assert equal with golden file
NSString *commandOutput =
[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:outputPath]
encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(goldenFileContents, commandOutput,
@"Metrics command command did not produce expected output");
}
- (void)testPrettyPrinting {
NSError *err;
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"Commands/testdata/metrics-prettyprint.txt"];
NSString *goldenFileContents = [[NSString alloc]
initWithData:[NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&err]
encoding:NSUTF8StringEncoding];
XCTAssertNil(err, @"failed to read golden file %@ for testPrettyPrinting", path);
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSString *outputPath = [NSString pathWithComponents:@[ self.tempDir, @"test.data" ]];
// redirect stdout
int fd = open([outputPath UTF8String], O_TRUNC | O_WRONLY | O_CREAT, 0600);
int saved_stdout = dup(fileno(stdout));
dup2(fd, fileno(stdout));
[metricsCmd prettyPrintMetrics:[SNTMetricFormatTestHelper createValidMetricsDictionary]
asJSON:NO];
// restore stdout
fflush(stdout);
dup2(saved_stdout, fileno(stdout));
// open test file assert equal with golden file
NSString *commandOutput =
[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:outputPath]
encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(goldenFileContents, commandOutput,
@"Metrics command command did not produce expected output");
}
- (void)testFiltering {
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSDictionary *metricDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
NSDictionary *filtered;
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[]];
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"], @"No filtering with no args");
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json" ]];
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"],
@"No filtering with no metric args");
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json", @"/santa" ]];
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 3,
@"Expected filter of metrics with /santa to return 3 metrics");
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"/build", @"/santa" ]];
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 4,
@"Expected filter of metrics with /build and /santa to return 4 metrics");
}
@end

View File

@@ -60,9 +60,11 @@ REGISTER_COMMAND_NAME(@"rule")
@" Will add the hash of the file currently at that path.\n"
@" Does not work with --check. Use the fileinfo verb to check.\n"
@" the rule state of a file.\n"
@" --sha256 {sha256}: hash to add/remove/check\n"
@" --identifier {sha256|teamID}: identifier to add/remove/check\n"
@" --sha256 {sha256}: hash to add/remove/check [deprecated]\n"
@"\n"
@" Optionally:\n"
@" --teamid: add or check a team ID rule instead of binary\n"
@" --certificate: add or check a certificate sha256 rule instead of binary\n"
#ifdef DEBUG
@" --force: allow manual changes even when SyncBaseUrl is set\n"
@@ -111,17 +113,24 @@ REGISTER_COMMAND_NAME(@"rule")
check = YES;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--teamid"] == NSOrderedSame) {
newRule.type = SNTRuleTypeTeamID;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--path requires an argument"];
}
path = arguments[i];
} else if ([arg caseInsensitiveCompare:@"--identifier"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--identifier requires an argument"];
}
newRule.identifier = arguments[i];
} else if ([arg caseInsensitiveCompare:@"--sha256"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--sha256 requires an argument"];
}
newRule.shasum = arguments[i];
if (newRule.shasum.length != 64) {
newRule.identifier = arguments[i];
if (newRule.identifier.length != 64) {
[self printErrorUsageAndExit:@"--sha256 requires a valid SHA-256 as the argument"];
}
} else if ([arg caseInsensitiveCompare:@"--message"] == NSOrderedSame) {
@@ -139,7 +148,7 @@ REGISTER_COMMAND_NAME(@"rule")
}
if (check) {
if (!newRule.shasum) return [self printErrorUsageAndExit:@"--check requires --sha256"];
if (!newRule.identifier) return [self printErrorUsageAndExit:@"--check requires --identifier"];
return [self printStateOfRule:newRule daemonConnection:self.daemonConn];
}
@@ -150,17 +159,18 @@ REGISTER_COMMAND_NAME(@"rule")
}
if (newRule.type == SNTRuleTypeBinary) {
newRule.shasum = fi.SHA256;
newRule.identifier = fi.SHA256;
} else if (newRule.type == SNTRuleTypeCertificate) {
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
newRule.shasum = cs.leafCertificate.SHA256;
newRule.identifier = cs.leafCertificate.SHA256;
} else if (newRule.type == SNTRuleTypeTeamID) {
}
}
if (newRule.state == SNTRuleStateUnknown) {
[self printErrorUsageAndExit:@"No state specified"];
} else if (!newRule.shasum) {
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
} else if (!newRule.identifier) {
[self printErrorUsageAndExit:@"Either SHA-256, team ID, or path to file must be specified"];
}
[[self.daemonConn remoteObjectProxy]
@@ -173,10 +183,25 @@ REGISTER_COMMAND_NAME(@"rule")
LOGD(@"Failure reason: %@", error.localizedFailureReason);
exit(1);
} else {
NSString *ruleType;
switch (newRule.type) {
case SNTRuleTypeCertificate:
case SNTRuleTypeBinary: {
ruleType = @"SHA-256";
break;
}
case SNTRuleTypeTeamID: {
ruleType = @"Team ID";
break;
}
default: ruleType = @"(Unknown type)";
}
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Removed rule for %s: %s.\n", [ruleType UTF8String],
[newRule.identifier UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Added rule for %s: %s.\n", [ruleType UTF8String],
[newRule.identifier UTF8String]);
}
exit(0);
}
@@ -184,14 +209,16 @@ REGISTER_COMMAND_NAME(@"rule")
}
- (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)daemonConn {
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.shasum : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.shasum : nil;
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.identifier : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.identifier : nil;
NSString *teamID = (rule.type == SNTRuleTypeTeamID) ? rule.identifier : nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
__block NSMutableString *output;
[[daemonConn remoteObjectProxy] decisionForFilePath:nil
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
reply:^(SNTEventState s) {
output = (SNTEventStateAllow & s)
? @"Allowed".mutableCopy
@@ -219,6 +246,10 @@ REGISTER_COMMAND_NAME(@"rule")
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID:
[output appendString:@" (TeamID)"];
break;
default: output = @"None".mutableCopy; break;
}
if (isatty(STDOUT_FILENO)) {
@@ -244,6 +275,7 @@ REGISTER_COMMAND_NAME(@"rule")
[[daemonConn remoteObjectProxy]
databaseRuleForBinarySHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
reply:^(SNTRule *r) {
if (r.state == SNTRuleStateAllowTransitive) {
NSDate *date =

View File

@@ -48,16 +48,10 @@ REGISTER_COMMAND_NAME(@"status")
dispatch_group_t group = dispatch_group_create();
// Daemon status
__block BOOL driverConnected;
__block NSString *clientMode;
__block uint64_t cpuEvents, ramEvents;
__block double cpuPeak, ramPeak;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] driverConnectionEstablished:^(BOOL connected) {
driverConnected = connected;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
switch (cm) {
case SNTClientModeMonitor: clientMode = @"Monitor"; break;
@@ -80,9 +74,9 @@ REGISTER_COMMAND_NAME(@"status")
SNTConfigurator *configurator = [SNTConfigurator configurator];
BOOL cachingEnabled = (![configurator enableSystemExtension] || [configurator enableSysxCache]);
BOOL cachingEnabled = [configurator enableSysxCache];
// Kext status
// Cache status
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
if (cachingEnabled) {
dispatch_group_enter(group);
@@ -94,17 +88,19 @@ REGISTER_COMMAND_NAME(@"status")
}
// Database counts
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1, teamIDRuleCount = -1;
__block int64_t compilerRuleCount = -1, transitiveRuleCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate,
int64_t compiler, int64_t transitive) {
binaryRuleCount = binary;
certRuleCount = certificate;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
[[self.daemonConn remoteObjectProxy]
databaseRuleCounts:^(int64_t binary, int64_t certificate, int64_t compiler, int64_t transitive,
int64_t teamID) {
binaryRuleCount = binary;
certRuleCount = certificate;
teamIDRuleCount = teamID;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseEventCount:^(int64_t count) {
eventCount = count;
@@ -172,16 +168,24 @@ REGISTER_COMMAND_NAME(@"status")
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
BOOL exportMetrics = [[SNTConfigurator configurator] exportMetrics];
NSURL *metricsURLStr = [[SNTConfigurator configurator] metricURL];
NSUInteger metricExportInterval = [[SNTConfigurator configurator] metricExportInterval];
if ([arguments containsObject:@"--json"]) {
NSMutableDictionary *stats = [@{
@"daemon" : @{
@"driver_connected" : @(driverConnected),
@"driver_connected" : @(YES),
@"mode" : clientMode ?: @"null",
@"file_logging" : @(fileLogging),
@"watchdog_cpu_events" : @(cpuEvents),
@"watchdog_ram_events" : @(ramEvents),
@"watchdog_cpu_peak" : @(cpuPeak),
@"watchdog_ram_peak" : @(ramPeak),
@"block_usb" : @(configurator.blockUSBMount),
@"remount_usb_mode" : (configurator.blockUSBMount && configurator.remountUSBMode.count
? configurator.remountUSBMode
: @""),
},
@"database" : @{
@"binary_rules" : @(binaryRuleCount),
@@ -213,19 +217,26 @@ REGISTER_COMMAND_NAME(@"status")
printf("%s\n", [statsStr UTF8String]);
} else {
printf(">>> Daemon Info\n");
printf(" %-25s | %s\n", "Driver Connected", driverConnected ? "Yes" : "No");
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
printf(" %-25s | %s\n", "USB Remounting Mode:",
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
}
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
if (cachingEnabled) {
printf(">>> Cache Info\n");
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
}
printf(">>> Database Info\n");
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %lld\n", "TeamID Rules", teamIDRuleCount);
printf(" %-25s | %lld\n", "Compiler Rules", compilerRuleCount);
printf(" %-25s | %lld\n", "Transitive Rules", transitiveRuleCount);
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
@@ -241,6 +252,12 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %s\n", "Bundle Scanning", (enableBundles ? "Yes" : "No"));
printf(" %-25s | %s\n", "Transitive Rules", (enableTransitiveRules ? "Yes" : "No"));
}
if (exportMetrics) {
printf(">>> Metrics Info\n");
printf(" %-25s | %s\n", "Metrics Server", [[metricsURLStr absoluteString] UTF8String]);
printf(" %-25s | %lu\n", "Export Interval (seconds)", metricExportInterval);
}
}
exit(0);

View File

@@ -0,0 +1,99 @@
/// 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/Foundation.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol, SNTSyncServiceLogReceiverXPC>
@end
@implementation SNTCommandSync
REGISTER_COMMAND_NAME(@"sync")
#pragma mark SNTCommand protocol methods
+ (BOOL)requiresRoot {
return YES;
}
+ (BOOL)requiresDaemonConn {
return NO; // We talk directly with the syncservice.
}
+ (NSString *)shortHelpText {
return @"Synchronizes Santa with a configured server.";
}
+ (NSString *)longHelpText {
return (@"If Santa is configured to synchronize with a server, "
@"this is the command used for syncing.\n\n"
@"Options:\n"
@" --clean: Perform a clean sync, erasing all existing rules and requesting a\n"
@" clean sync from the server.");
}
- (void)runWithArguments:(NSArray *)arguments {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
LOGE(@"Failed to drop root privileges. Exiting.");
exit(1);
}
if (![[SNTConfigurator configurator] syncBaseURL]) {
LOGE(@"Missing SyncBaseURL. Exiting.");
exit(1);
}
MOLXPCConnection *ss = [SNTXPCSyncServiceInterface configuredConnection];
ss.invalidationHandler = ^(void) {
LOGE(@"Failed to connect to the sync service.");
exit(1);
};
[ss resume];
NSXPCListener *logListener = [NSXPCListener anonymousListener];
MOLXPCConnection *lr = [[MOLXPCConnection alloc] initServerWithListener:logListener];
lr.exportedObject = self;
lr.unprivilegedInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceLogReceiverXPC)];
[lr resume];
BOOL isClean = [NSProcessInfo.processInfo.arguments containsObject:@"--clean"];
[[ss remoteObjectProxy]
syncWithLogListener:logListener.endpoint
isClean:isClean
reply:^(SNTSyncStatusType status) {
if (status == SNTSyncStatusTypeTooManySyncsInProgress) {
[self didReceiveLog:@"Too many syncs in progress, try again later."];
}
exit((int)status);
}];
// Do not return from this scope.
[[NSRunLoop mainRunLoop] run];
}
/// Implement the SNTSyncServiceLogReceiverXPC protocol.
- (void)didReceiveLog:(NSString *)log {
printf("%s\n", log.UTF8String);
fflush(stdout);
}
@end

View File

@@ -13,13 +13,12 @@
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <IOKit/kext/KextManager.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommon.h"
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTFileInfo.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@@ -50,7 +49,6 @@ REGISTER_COMMAND_NAME(@"version")
- (void)runWithArguments:(NSArray *)arguments {
if ([arguments containsObject:@"--json"]) {
NSDictionary *versions = @{
@"santa-driver" : [self santaKextVersion],
@"santad" : [self santadVersion],
@"santactl" : [self santactlVersion],
@"SantaGUI" : [self santaAppVersion],
@@ -62,7 +60,6 @@ REGISTER_COMMAND_NAME(@"version")
encoding:NSUTF8StringEncoding];
printf("%s\n", [versionsStr UTF8String]);
} else {
printf("%-15s | %s\n", "santa-driver", [[self santaKextVersion] UTF8String]);
printf("%-15s | %s\n", "santad", [[self santadVersion] UTF8String]);
printf("%-15s | %s\n", "santactl", [[self santactlVersion] UTF8String]);
printf("%-15s | %s\n", "SantaGUI", [[self santaAppVersion] UTF8String]);
@@ -70,38 +67,25 @@ REGISTER_COMMAND_NAME(@"version")
exit(0);
}
- (NSString *)santaKextVersion {
if ([[SNTConfigurator configurator] enableSystemExtension]) {
return @"un-needed (SystemExtension being used)";
}
NSDictionary *loadedKexts = CFBridgingRelease(KextManagerCopyLoadedKextInfo(
(__bridge CFArrayRef) @[ @(USERCLIENT_ID) ], (__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
if (loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"]) {
return loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"];
}
SNTFileInfo *driverInfo = [[SNTFileInfo alloc] initWithPath:@(kKextPath)];
if (driverInfo) {
return [driverInfo.bundleVersion stringByAppendingString:@" (unloaded)"];
}
return @"not found";
- (NSString *)composeVersionsFromDict:(NSDictionary *)dict {
if (!dict[@"CFBundleVersion"]) return @"";
NSString *productVersion = dict[@"CFBundleShortVersionString"];
NSString *buildVersion = [[dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
return [NSString stringWithFormat:@"%@ (build %@)", productVersion, buildVersion];
}
- (NSString *)santadVersion {
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaDPath)];
return daemonInfo.bundleVersion ?: @"";
return [self composeVersionsFromDict:daemonInfo.infoPlist];
}
- (NSString *)santaAppVersion {
SNTFileInfo *guiInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaAppPath)];
return guiInfo.bundleVersion ?: @"";
return [self composeVersionsFromDict:guiInfo.infoPlist];
}
- (NSString *)santactlVersion {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] ?: @"";
return [self composeVersionsFromDict:[[NSBundle mainBundle] infoDictionary]];
}
@end

View File

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

View File

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

View File

@@ -1,567 +0,0 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncFCM.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
static NSString *const kFCMActionKey = @"action";
static NSString *const kFCMFileHashKey = @"file_hash";
static NSString *const kFCMFileNameKey = @"file_name";
static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@interface SNTCommandSyncManager () {
SCNetworkReachabilityRef _reachability;
}
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@property(nonatomic) NSCache *dispatchLock;
// allowlistNotifications dictionary stores info from FCM messages. The binary/bundle hash is used
// as a key mapping to values that are themselves dictionaries. These dictionary values contain the
// name of the binary/bundle and a count of associated binary rules.
@property(nonatomic) NSMutableDictionary *allowlistNotifications;
// allowlistNotificationQueue is used to serialize access to the allowlistNotifications dictionary.
@property(nonatomic) NSOperationQueue *allowlistNotificationQueue;
@property NSUInteger fullSyncInterval;
@property NSUInteger FCMFullSyncInterval;
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property SNTCommandSyncFCM *FCMClient;
@property NSString *FCMToken;
@property(nonatomic) MOLXPCConnection *daemonConn;
@property BOOL targetedRuleSync;
@property(nonatomic) BOOL reachable;
@end
// Called when the network state changes
static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
void *info) {
// Put this check and set on the main thread to ensure serial access.
dispatch_async(dispatch_get_main_queue(), ^{
SNTCommandSyncManager *commandSyncManager = (__bridge SNTCommandSyncManager *)info;
// Only call the setter when there is a change. This will filter out the redundant calls to this
// callback whenever the network interface states change.
if (commandSyncManager.reachable != (flags & kSCNetworkReachabilityFlagsReachable)) {
commandSyncManager.reachable = (flags & kSCNetworkReachabilityFlagsReachable);
}
});
}
@implementation SNTCommandSyncManager
#pragma mark init
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn isDaemon:(BOOL)daemon {
self = [super init];
if (self) {
_daemonConn = daemonConn;
_daemon = daemon;
_fullSyncTimer = [self createSyncTimerWithBlock:^{
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.FCMFullSyncInterval];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kFullSync];
[self preflight];
[self unlockAction:kFullSync];
}];
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
dispatch_source_set_timer(self.ruleSyncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER,
0);
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kRuleSync];
SNTCommandSyncState *syncState = [self createSyncState];
syncState.targetedRuleSync = self.targetedRuleSync;
syncState.allowlistNotifications = self.allowlistNotifications;
syncState.allowlistNotificationQueue = self.allowlistNotificationQueue;
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
} else {
LOGE(@"Rule download failed");
}
self.targetedRuleSync = NO;
[self unlockAction:kRuleSync];
}];
_dispatchLock = [[NSCache alloc] init];
_allowlistNotifications = [NSMutableDictionary dictionary];
_allowlistNotificationQueue = [[NSOperationQueue alloc] init];
_allowlistNotificationQueue.maxConcurrentOperationCount = 1; // make this a serial queue
_fullSyncInterval = kDefaultFullSyncInterval;
_eventBatchSize = kDefaultEventBatchSize;
_FCMFullSyncInterval = kDefaultFCMFullSyncInterval;
_FCMGlobalRuleSyncDeadline = kDefaultFCMGlobalRuleSyncDeadline;
}
return self;
}
- (void)dealloc {
// Ensure reachability is always stopped
[self stopReachability];
}
#pragma mark SNTSyncdXPC protocol methods
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle {
SNTCommandSyncState *syncState = [self createSyncState];
if (isFromBundle) syncState.eventBatchSize = self.eventBatchSize;
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if (events && [p uploadEvents:events]) {
LOGD(@"Events upload complete");
} else {
LOGE(@"Events upload failed. Will retry again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
}
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply {
if (!event) {
reply(SNTBundleEventActionDropEvents);
return;
}
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p uploadEvents:@[ event ]]) {
if ([syncState.bundleBinaryRequests containsObject:event.fileBundleHash]) {
reply(SNTBundleEventActionSendEvents);
LOGD(@"Needs related events");
} else {
reply(SNTBundleEventActionDropEvents);
LOGD(@"Bundle event upload complete");
}
} else {
// Related bundle events will be stored and eventually synced, whether the server actually
// wanted them or not. If they weren't needed the server will simply ignore them.
reply(SNTBundleEventActionStoreEvents);
LOGE(@"Bundle event upload failed. Will retry again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply(self.FCMClient.isConnected);
}
#pragma mark push notification methods
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
if ([self.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Already listening for push notifications");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTCommandSyncFCM alloc] initWithProject:config.fcmProject
entity:config.fcmEntity
apiKey:config.fcmAPIKey
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || message[@"noOp"]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message
withMachineID:machineID];
}];
self.FCMClient.tokenHandler = ^(NSString *t) {
STRONGIFY(self);
LOGD(@"tokenHandler: %@", t);
self.FCMToken = t;
[self preflightOnly:YES];
};
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.FCMToken = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
[self.FCMClient connect];
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSDictionary *message = [self messageFromMessageData:[self messageDataFromFCMmessage:FCMmessage]];
if (!message) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSString *action = message[kFCMActionKey];
if (!action) {
LOGD(@"Push notification message contains no action");
return;
}
// We assume that the incoming FCM message contains name of binary/bundle and a hash. Rule count
// info for bundles will be sent out later with the rules themselves. If the message is related
// to a bundle, the hash is a bundle hash, otherwise it is just a hash for a single binary.
// For later use, we store a mapping of bundle/binary hash to a dictionary containing the
// binary/bundle name so we can send out relevant notifications once the rules are actually
// downloaded & added to local database. We use a dictionary value so that we can later add a
// count field when we start downloading the rules and receive the count information.
NSString *fileHash = message[kFCMFileHashKey];
NSString *fileName = message[kFCMFileNameKey];
if (fileName && fileHash) {
[self.allowlistNotificationQueue addOperationWithBlock:^{
self.allowlistNotifications[fileHash] = @{kFileName : fileName}.mutableCopy;
}];
}
LOGD(@"Push notification action: %@ received", action);
if ([action isEqualToString:kFullSync]) {
[self fullSync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetHostID = message[kFCMTargetHostIDKey];
if (targetHostID && [targetHostID caseInsensitiveCompare:machineID] == NSOrderedSame) {
LOGD(@"Targeted rule_sync for host_id: %@", targetHostID);
self.targetedRuleSync = YES;
[self ruleSync];
} else {
uint32_t delaySeconds = arc4random_uniform((uint32_t)self.FCMGlobalRuleSyncDeadline);
LOGD(@"Global rule_sync, staggering: %u second delay", delaySeconds);
[self ruleSyncSecondsFromNow:delaySeconds];
}
} else if ([action isEqualToString:kConfigSync]) {
[self fullSync];
} else if ([action isEqualToString:kLogSync]) {
[self fullSync];
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
- (NSData *)messageDataFromFCMmessage:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
if (!messageData) {
LOGD(@"Unable to parse push notification message data");
return nil;
}
NSError *error;
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!rawMessage) {
LOGD(@"Unable to parse push notification message data: %@", error);
return nil;
}
// Create a new message dropping unexpected values
NSArray *allowedKeys = @[ kFCMActionKey, kFCMFileHashKey, kFCMFileNameKey, kFCMTargetHostIDKey ];
NSMutableDictionary *message = [NSMutableDictionary dictionaryWithCapacity:allowedKeys.count];
for (NSString *key in allowedKeys) {
if ([rawMessage[key] isKindOfClass:[NSString class]] && [rawMessage[key] length]) {
message[key] = rawMessage[key];
}
}
return message.count ? [message copy] : nil;
}
#pragma mark sync timer control
- (void)fullSync {
[self fullSyncSecondsFromNow:0];
}
- (void)fullSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kFullSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kFullSync);
return;
}
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)ruleSync {
[self ruleSyncSecondsFromNow:0];
}
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kRuleSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kRuleSync);
return;
}
[self rescheduleTimerQueue:self.ruleSyncTimer secondsFromNow:seconds];
}
- (void)rescheduleTimerQueue:(dispatch_source_t)timerQueue secondsFromNow:(uint64_t)seconds {
uint64_t interval = seconds * NSEC_PER_SEC;
uint64_t leeway = (seconds * 0.5) * NSEC_PER_SEC;
dispatch_source_set_timer(timerQueue, dispatch_walltime(NULL, interval), interval, leeway);
}
#pragma mark syncing chain
- (void)preflight {
[self preflightOnly:NO];
}
- (void)preflightOnly:(BOOL)preflightOnly {
LOGD(@"Preflight starting");
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
// Clean up reachability if it was started for a non-network error
[self stopReachability];
self.eventBatchSize = syncState.eventBatchSize;
// Start listening for push notifications with a full sync every FCMFullSyncInterval
if (syncState.daemon && [SNTConfigurator configurator].fcmEnabled) {
self.FCMFullSyncInterval = syncState.FCMFullSyncInterval;
self.FCMGlobalRuleSyncDeadline = syncState.FCMGlobalRuleSyncDeadline;
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCM not enabled. Sync every %lu min.", syncState.fullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.fullSyncInterval = syncState.fullSyncInterval;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.fullSyncInterval];
}
if (preflightOnly) return;
return [self eventUploadWithSyncState:syncState];
} else {
if (!syncState.daemon) {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
}
- (void)eventUploadWithSyncState:(SNTCommandSyncState *)syncState {
LOGD(@"Event upload starting");
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownloadWithSyncState:syncState];
} else {
LOGE(@"Event upload failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)ruleDownloadWithSyncState:(SNTCommandSyncState *)syncState {
LOGD(@"Rule download starting");
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
return [self postflightWithSyncState:syncState];
} else {
LOGE(@"Rule download failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
LOGD(@"Postflight starting");
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
if (!syncState.daemon) exit(0);
} else {
LOGE(@"Postflight failed");
if (!syncState.daemon) exit(1);
}
}
#pragma mark internal helpers
- (dispatch_source_t)createSyncTimerWithBlock:(void (^)(void))block {
dispatch_source_t timerQueue =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_source_set_event_handler(timerQueue, block);
dispatch_resume(timerQueue);
return timerQueue;
}
- (SNTCommandSyncState *)createSyncState {
// Gather some data needed during some sync stages
SNTCommandSyncState *syncState = [[SNTCommandSyncState alloc] init];
SNTConfigurator *config = [SNTConfigurator configurator];
syncState.syncBaseURL = config.syncBaseURL;
if (syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (!syncState.daemon) exit(1);
} else if (![syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
syncState.machineID = config.machineID;
if (syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
if (!syncState.daemon) exit(1);
}
syncState.machineOwner = config.machineOwner;
if (syncState.machineOwner.length == 0) {
syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
syncState.xsrfToken = token;
dispatch_group_leave(group);
}];
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
};
// Configure server auth
if ([config syncServerAuthRootsFile]) {
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
// Configure client auth
if ([config syncClientAuthCertificateFile]) {
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
} else if ([config syncClientAuthCertificateCn]) {
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
} else if ([config syncClientAuthCertificateIssuer]) {
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
syncState.compressedContentEncoding =
config.enableBackwardsCompatibleContentEncoding ? @"zlib" : @"deflate";
syncState.FCMToken = self.FCMToken;
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return syncState;
}
- (void)lockAction:(NSString *)action {
[self.dispatchLock setObject:@YES forKey:action];
}
- (void)unlockAction:(NSString *)action {
[self.dispatchLock removeObjectForKey:action];
}
- (BOOL)checkLockAction:(NSString *)action {
return ([self.dispatchLock objectForKey:action] == nil);
}
#pragma mark reachability methods
- (void)setReachable:(BOOL)reachable {
_reachable = reachable;
if (reachable) {
[self stopReachability];
[self fullSync];
}
}
// Start listening for network state changes on a background thread
- (void)startReachability {
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_reachability) return;
const char *nodename = [[SNTConfigurator configurator] syncBaseURL].host.UTF8String;
self->_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, nodename);
SCNetworkReachabilityContext context = {
.info = (__bridge_retained void *)self,
.release = (void (*)(const void *))CFBridgingRelease,
};
if (SCNetworkReachabilitySetCallback(self->_reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
self->_reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
} else {
[self stopReachability];
}
});
}
// Stop listening for network state changes
- (void)stopReachability {
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_reachability) {
SCNetworkReachabilitySetDispatchQueue(self->_reachability, NULL);
if (self->_reachability) CFRelease(self->_reachability);
self->_reachability = NULL;
}
});
}
@end

View File

@@ -1 +0,0 @@
{"rules": [{"rule_type": "CERTIFICATE", "policy": "BLACKLIST", "sha256": "7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142", "custom_msg": "Hi There"}]}

View File

@@ -0,0 +1,118 @@
{
"metrics" : {
"\/santa\/rules" : {
"type" : 7,
"description" : "Number of rules",
"fields" : {
"rule_type" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "binary",
"data" : 1
},
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "certificate",
"data" : 3
}
]
}
},
"\/proc\/memory\/resident_size" : {
"type" : 7,
"description" : "The resident set size of this process",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : 123456789
}
]
}
},
"\/santa\/events" : {
"type" : 9,
"description" : "Count of process exec events on the host",
"fields" : {
"rule_type" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "binary",
"data" : 1
},
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "certificate",
"data" : 2
}
]
}
},
"\/santa\/using_endpoint_security_framework" : {
"type" : 1,
"description" : "Is santad using the endpoint security framework",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : true
}
]
}
},
"\/proc\/birth_timestamp" : {
"type" : 3,
"description" : "Start time of this santad instance, in microseconds since epoch",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : 1250999830800
}
]
}
},
"\/proc\/memory\/virtual_size" : {
"type" : 7,
"description" : "The virtual memory size of this process",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : 987654321
}
]
}
},
"\/build\/label" : {
"type" : 2,
"description" : "Software version running",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : "20210809.0.1"
}
]
}
}
},
"root_labels" : {
"hostname" : "testHost",
"username" : "testUser"
}
}

View File

@@ -0,0 +1,69 @@
>>> Metrics Info
Metrics Server | http://localhost:2444/submit
Metrics Format | monarchjson
Export Interval (seconds) | 30
>>> Root Labels
hostname | testHost
username | testUser
>>> Metrics
Metric Name | /santa/rules
Description | Number of rules
Type | SNTMetricTypeGaugeInt64
Field | rule_type=binary
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1
Field | rule_type=certificate
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 3
Metric Name | /proc/memory/resident_size
Description | The resident set size of this process
Type | SNTMetricTypeGaugeInt64
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 123456789
Metric Name | /santa/events
Description | Count of process exec events on the host
Type | SNTMetricTypeCounter
Field | rule_type=binary
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1
Field | rule_type=certificate
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 2
Metric Name | /santa/using_endpoint_security_framework
Description | Is santad using the endpoint security framework
Type | SNTMetricTypeConstantBool
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1
Metric Name | /proc/birth_timestamp
Description | Start time of this santad instance, in microseconds since epoch
Type | SNTMetricTypeConstantInt64
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1250999830800
Metric Name | /proc/memory/virtual_size
Description | The virtual memory size of this process
Type | SNTMetricTypeGaugeInt64
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 987654321
Metric Name | /build/label
Description | Software version running
Type | SNTMetricTypeConstantString
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 20210809.0.1

View File

@@ -1,10 +1,14 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_bundle")
load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
)
licenses(["notice"])
objc_library(
name = "santad_lib",
name = "database_controller",
srcs = [
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTDatabaseTable.m",
@@ -12,19 +16,161 @@ objc_library(
"DataLayer/SNTEventTable.m",
"DataLayer/SNTRuleTable.h",
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"SNTDatabaseController.h",
"SNTDatabaseController.m",
],
deps = [
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
],
)
objc_library(
name = "SNTEventProvider",
hdrs = ["EventProviders/SNTEventProvider.h"],
deps = [
"//Source/common:SNTCommon",
],
)
objc_library(
name = "endpoint_security_manager",
srcs = [
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTEventProvider.h",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":SNTEventProvider",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
)
objc_library(
name = "event_logs_common",
srcs = [
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTRuleTable.h",
"Logs/SNTEventLog.h",
"Logs/SNTEventLog.m",
"Logs/SNTFileEventLog.h",
"Logs/SNTFileEventLog.m",
"Logs/SNTProtobufEventLog.h",
"Logs/SNTSyslogEventLog.h",
"SNTDatabaseController.h",
],
hdrs = [
"Logs/SNTEventLog.h",
],
deps = [
":database_controller",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"@FMDB",
],
)
objc_library(
name = "protobuf_event_logs",
srcs = [
"Logs/SNTLogOutput.h",
"Logs/SNTProtobufEventLog.h",
"Logs/SNTProtobufEventLog.m",
"Logs/SNTSimpleMaildir.h",
"Logs/SNTSimpleMaildir.m",
],
deps = [
":event_logs_common",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTStoredEvent",
"//Source/common:santa_objc_proto",
],
)
objc_library(
name = "syslog_event_logs",
srcs = [
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEventProvider.h",
"Logs/SNTSyslogEventLog.h",
"Logs/SNTSyslogEventLog.m",
],
deps = [
":endpoint_security_manager",
":event_logs_common",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTLogging",
],
)
objc_library(
name = "file_event_logs",
srcs = [
"Logs/SNTFileEventLog.h",
"Logs/SNTFileEventLog.m",
"Logs/SNTSyslogEventLog.h",
],
deps = [
":event_logs_common",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTStrengthify",
],
)
objc_library(
name = "event_logs",
deps = [
":file_event_logs",
":protobuf_event_logs",
":syslog_event_logs",
"//Source/common:SNTCommon",
],
hdrs = ["Logs/SNTEventLog.h"],
)
objc_library(
name = "santad_lib",
srcs = [
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTEventTable.h",
"DataLayer/SNTRuleTable.h",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTDeviceManager.h",
"EventProviders/SNTDeviceManager.mm",
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEventProvider.h",
"SNTApplication.h",
"SNTApplication.m",
"SNTCompilerController.h",
@@ -32,7 +178,6 @@ objc_library(
"SNTDaemonControlController.h",
"SNTDaemonControlController.m",
"SNTDatabaseController.h",
"SNTDatabaseController.m",
"SNTExecutionController.h",
"SNTExecutionController.m",
"SNTNotificationQueue.h",
@@ -52,55 +197,102 @@ objc_library(
"IOKit",
],
deps = [
":database_controller",
":endpoint_security_manager",
":event_logs",
":SNTApplicationCoreMetrics",
"//Source/common:SantaCache",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTPrefixTree",
"//Source/common:SNTMetricSet",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
objc_library(
name = "SNTApplicationCoreMetrics",
srcs = ["SNTApplicationCoreMetrics.m"],
hdrs = ["SNTApplicationCoreMetrics.h"],
deps = [
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
],
)
objc_library(
name = "EndpointSecurityTestLib",
testonly = 1,
srcs = [
"EventProviders/EndpointSecurityTestUtil.h",
"EventProviders/EndpointSecurityTestUtil.mm",
],
testonly = 1,
hdrs = [
"EventProviders/EndpointSecurityTestUtil.h",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
sdk_frameworks = [
"DiskArbitration",
"IOKit",
],
)
objc_library(
name = "DiskArbitrationTestLib",
testonly = 1,
srcs = [
"EventProviders/DiskArbitrationTestUtil.h",
"EventProviders/DiskArbitrationTestUtil.mm",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
sdk_frameworks = [
"DiskArbitration",
"IOKit",
],
)
macos_bundle(
name = "com.google.santa.daemon",
bundle_extension = "systemextension",
bundle_id = "com.google.santa.daemon",
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Daemon_Dev.provisionprofile",
"//conditions:default": "//profiles:daemon_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
@@ -110,48 +302,34 @@ macos_bundle(
santa_unit_test(
name = "SNTExecutionControllerTest",
srcs = [
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTDatabaseTable.m",
"DataLayer/SNTEventTable.h",
"DataLayer/SNTEventTable.m",
"DataLayer/SNTRuleTable.h",
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEventProvider.h",
"Logs/SNTEventLog.h",
"Logs/SNTEventLog.m",
"SNTDatabaseController.h",
"SNTDatabaseController.m",
"SNTExecutionController.h",
"SNTExecutionController.m",
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTEventTable.h",
"DataLayer/SNTRuleTable.h",
"EventProviders/SNTEventProvider.h",
"SNTExecutionControllerTest.m",
"SNTNotificationQueue.h",
"SNTNotificationQueue.m",
"SNTPolicyProcessor.h",
"SNTPolicyProcessor.m",
"SNTSyncdQueue.h",
"SNTSyncdQueue.m",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
"//Source/common:SantaCache",
":santad_lib",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTPrefixTree",
"//Source/common:SNTRule",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SantaCache",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
@@ -188,6 +366,7 @@ santa_unit_test(
],
deps = [
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
@@ -196,47 +375,160 @@ santa_unit_test(
"@MOLCertificate",
"@MOLCodesignChecker",
],
sdk_dylibs = [
"EndpointSecurity",
],
)
santa_unit_test(
name = "SNTEndpointSecurityManagerTest",
srcs = [
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEndpointSecurityManagerTest.mm",
"EventProviders/SNTEventProvider.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEndpointSecurityManager.h",
],
deps = [
":EndpointSecurityTestLib",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
)
santa_unit_test(
name = "SNTApplicationTest",
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
srcs = [
"SNTApplicationTest.m"
],
deps = [
":santad_lib",
":EndpointSecurityTestLib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
"//Source/common:SNTConfigurator",
"//Source/common:SNTCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
)
santa_unit_test(
name = "SNTDeviceManagerTest",
srcs = [
"EventProviders/DiskArbitrationTestUtil.h",
"EventProviders/SNTDeviceManager.h",
"EventProviders/SNTDeviceManagerTest.mm",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":DiskArbitrationTestLib",
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationTest",
srcs = [
"SNTApplication.h",
"SNTApplicationTest.m",
"SNTDatabaseController.h",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTConfigurator",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
tags = ["exclusive"],
)
santa_unit_test(
name = "SNTApplicationBenchmark",
srcs = [
"SNTApplicationBenchmark.m",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationCoreMetricsTest",
srcs = [
"SNTApplicationCoreMetricsTest.m",
],
minimum_os_version = "10.15",
deps = [
":SNTApplicationCoreMetrics",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
"@OCMock",
],
)
santa_unit_test(
name = "SNTProtobufEventLogTest",
srcs = [
"Logs/SNTLogOutput.h",
"Logs/SNTProtobufEventLog.h",
"Logs/SNTProtobufEventLogTest.m",
"Logs/SNTSimpleMaildir.h",
],
minimum_os_version = "10.15",
deps = [
":EndpointSecurityTestLib",
":event_logs",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:santa_objc_proto",
"@OCMock",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTApplicationCoreMetricsTest",
":SNTApplicationTest",
":SNTDeviceManagerTest",
":SNTEndpointSecurityManagerTest",
":SNTEventTableTest",
":SNTExecutionControllerTest",
":SNTProtobufEventLogTest",
":SNTRuleTableTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -51,12 +51,18 @@
///
- (NSUInteger)certificateRuleCount;
///
/// @return Number of team ID rules in the database
///
- (NSUInteger)teamIDRuleCount;
///
/// @return Rule for binary or certificate with given SHA-256. The binary rule will be returned
/// if it exists. If not, the certificate rule will be returned if it exists.
///
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256;
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID;
///
/// Add an array of rules to the database. The rules will be added within a transaction and the

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