Compare commits

...

122 Commits

Author SHA1 Message Date
Matt W
72969a3c92 Fix crash flushing cache on unmount events (#895) 2022-09-27 21:54:35 -04:00
Matt W
d2dbed78dd Return a value from the test block (#894) 2022-09-27 15:07:20 -04:00
Matt W
8fa91e4ff0 Build deps (#893)
* Too bad we can't require explicit build deps...

* More deps
2022-09-23 13:55:48 -04:00
Matt W
551763146d Linter and BUILD deps fixups (#892)
* Minor changes to address lint issues

* Add more BUILD deps

* Include cleanup

* Even more BUILD deps

* Still more BUILD deps
2022-09-23 11:18:58 -04:00
Matt W
7a7f0cd5a8 Ingestion fixups (#891) 2022-09-22 12:30:34 -04:00
Matt W
fcb49701b3 ES and Logging Interfaces Redesign (#888)
* Initial structure for ES wrappers, enriched types, logging

* Basic working ES and logging functionality

* Add in oneTBB and thread-safe-lru deps

* Added a bunch of enriched types

* Auto-mute self when establishing ES client

* Basic auth, tamper client. Syslog of all events. Basic compiler tracking.

* Update copyright header blobs, convert some tabs to spaces

* Auth result cache. Fix getting translocation path.

* Added remaining cache methods

* Add AuthResultCache to Recorder client. Cache now operates on es_file_t.

* Hooked up SNTPrefixTree

* Fix CompilerController for RENAME. Fix AllowList logging missing path.

* Block loading Santa kext

* Added device manager client

* Properly log DiskAppear events

* Fix build to adopt new adhoc build

* Handle clearing cache on UNMOUNT events

* Ignore other ES clients if configured

* Remove SNTAllowlistInfo. Rename AllowList to Allowlist. Minor cleanup.

* Recorder now logs asynchronously. Enricher now returns shared_ptrs.

* Added File writer. Added timestamps to BasicStream serializer.

* Skip calling stat in SNTFileInfo when path given by ES.

* Fix build issue

* Address draft PR feedback

* santactl integrated, XPC works, fix file writer bug

* Integrate syncservice. Start observing some config changes.

* Add metrics service wrapper

* Add metrics config observers and metrics interval reset.

* Start better dependency control. Add Null logger support.

* Added more deps

* Added more deps

* Fix issue where metric service wasn't starting

* Add missing variant include

* Fix missing parent proc name

* Added googletest and new unit test macro

* Started expanding AuthResultCacheTest

* Properly mock EndpointSecurityAPI

* Finished AuthResultCacheTest

* bazelrc now builds all C++ as C++17. Added LoggerTest.

* Add FileTest. Abstract some File constants to Logger.

* Added Empty serializer test

* Started work on BasicStringTest. Fixed some BasicString serialization bugs.

* Added Unlink BasicString serialization test

* Added some more tests. Commonized some test code

* Finished BasicStringTest. Converted to XCTest.

* Standardize esapi variable naming

* Bubble up gTest expect failures to XCTest failures

* AuthResultCacheTest now uses XCTest. Added common TestUtils.h

* EmptyTest now uses XCTest.

* FileTest now uses XCTest

* LoggerTest now uses XCTest. Removed santa_unit_gtest bazel macro.

* Added ClientTest

* Add basic Enricher tests

* Add MessageTest. Make more TestUtils.

* Rename metrics to Metrics

* Add MetricsTest.

* Apply template pattern to Serializer

* Add SNTDecisionCacheTest.

* Add SNTCachedDecisionTest.

* Testing with coveralls debug mode

* Allow manual CI runs

* Remove unused property

* Started work on SNTEndpointSecurityClientTest.

* WIP SNTEndpointSecurityClientTest, fix test run issue

* Added more base ES client tests

* Add more base ES client tests

* Base ES client tests done. Added serializer utils/tests. Expanded basic string tests.

* Add utils test to test suite

* Add copy ctor. Add test output to bazel coverage.

* Single thread bazel coverage

* Updaload coverage file

* Updaload coverage file

* Old gen cov test

* Restructure message handlers to enable better testability

* Added enable tests for all ES clients

* Made a single MockEndpointSecurityAPI class to share everywhere

* Added most of SNTCompilerControllerTest

* Cleanup SNTCompilerControllerTest

* Started expanding Auth client test

* Finished up the Authorizer tests

* Move to using enum class for notify/auth instead of bool

* WIP for tamper resistance test. ASAN issues.

* Add OCMock patch to fix test issue on ARM Macs

* Changed patches directory name to external_patches

* Update WORKSPACE path

* Finished up Tamper Resistance tests

* Finished up Recorder tests.

* Move SNTExecutionControllerTest to ObjC++

* Initial work to port SNTExecutionControllerTest

* Finished porting SNTExecutionControllerTest.

* Added SNTExecutionControllerTest to list of unit tests

* Ported SNTEndpointSecurityDeviceManager.

* Test cleanup, use MockESAPI expectation helpers

* Verify SNTEndpointSecurityDeviceManager expectations differently

* Test cleanup, omit gTest param list where unused

* Log message cleanup

* Rename SNTApplicationTest to santad_test.mm

* Finished porting santad_test, formerly SNTApplicationTest

* Fix SNTEndpointSecurityDeviceManager issues

* Pulled in missed fixes. Updated tests.

* Renamed lowercase filenames to match rest of codebase

* Fix non-static dispatch_once_t, and noisy watching compiler log message

* WIP Started process of removing components no longer used

* WIP Continued process of removing components no longer used

* BUILD file cleanup. Proto warning. Removed unused global

* Rename SNTEventProvider to SNTEndpointSecurityEventHandler

* Rename SNTEndpointSecurityEventHandler protocol

* Remove EnableSysxCache option. Remove --quick flag used during dev.

* Ran testing/fix.sh

* Addmissing param to fix.sh that was omitting .mm files.

* clang-format

* Fix linter: find cmd missing .mm ext, git grep exclude patch files.

* Use MakeESProcess default params in tests

* Move variables to camelCase in objc classes

* More case changes

* Sanitize strings

* Change dispatch queue priorities and standardize daemon queue naming

* Exclude patch files in markdown check

* Ensure string log messages end with newline

* Fix BasicStringTest

* Disable clang-format in code producing different results in local/remote versions

* Moved to using date ranges in copyright notices as per current guidelines

* Update Source/common/SNTConfigurator.h

Suggestion adding whitespace in comment to fix clang-format mangling

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>

* Removed santa_panic macro used in one place

* Updated comment about ES cachability

* Pin oneTBB to specific commit

* Address outstanding WORKSPACE 'canonical reproducible form' messages

* Use string append instead of ostringstream due to benchmark results

* Remove use of freind classes in EnrichedTypes.h

* Added SNTKVOManager, removed observers from SNTConfigurator.

* Fixed SNTEndpointSecurityRecorderTest class name

* Reduce usage of the auto keyword

* Each SNTKVOManager instance now adds its own observer

* Replaced more auto keywords with real types.

* Remove leftover code coverage debugging from ci.yml

* Updated comment

* Memoize SNTFileInfo sha256. Reduce some cache sizes.

* Fix issue checking for translocated paths

* Use more performant NSURL creation method

* Fix lint issue

* Address PR feedback

* Use an array literal for kvo objects

* Fix some clang tidy and import issues

* Replace third party LRU cache with SantaCache for now

* Fix clang tidy issues

* Address PR feedback

* Fix comment typo

Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>

* Added todo for when we adopt macOS 13

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>
2022-09-22 10:18:41 -04:00
Russell Hancox
c9ef723fc5 Project: Update bazel and apple-rules (#887) 2022-08-29 17:52:27 -04:00
Pete Markowsky
dc6732ef04 Refactor the SNTApplicationTest unit tests to function correctly (#885)
* Refactor the SNTApplicationTest unit tests to function correctly.

The tests were originally written in a table style and were impacted by the lack of mocking the configurator. This caused issues with static rules to impact the unit tests.

Additionally added improved logging messages for critical binaries and a todo for macOS 13 unit tests.

Added goodbinary and rules.db test files to allstar's ignored paths.
2022-08-29 13:18:04 -04:00
Russell Hancox
a48900a4ae Allstar: Pre-emptively check-in binary_artifacts.yaml to exclude test binaries (#884) 2022-08-25 09:32:43 -04:00
Russell Hancox
bb49118d94 README: Try again, this time replacing the correct bit (#883) 2022-08-24 16:26:30 -04:00
Russell Hancox
456333d6d2 README: Fix logo link, remove coverage badge (#882) 2022-08-24 16:22:37 -04:00
Pete Markowsky
fd23a5c3b7 Fix up endTimestamp to be Monarch compliant (#879)
Fix up endTimestamp field to be Monarch compliant.
2022-08-16 22:32:29 -04:00
Russell Hancox
ec203e8796 Project: Rename Source/santa -> Source/gui (#877) 2022-08-12 14:19:01 -04:00
Russell Hancox
57ff69208d GUI: Missed a required dependency (#876) 2022-08-12 14:02:22 -04:00
Russell Hancox
f00b7d2ded GUI: Expose SNTNotificationManager.h for the test. (#875) 2022-08-12 13:46:25 -04:00
Russell Hancox
9791fdd53c Project: Add a GH action to prevent trailing whitespace (#873) 2022-08-12 12:46:11 -04:00
Russell Hancox
26e2203f1e GUI: Improve signing chain key reporting in distributed notifications. (#874)
Also add a group for GUI unit_tests and include in the overall project tests group.
2022-08-12 11:03:21 -04:00
Russell Hancox
4a47195d12 Santa: Post distributed notification when showing block UI (#870)
Fixes #869
2022-08-11 12:34:35 -04:00
Russell Hancox
4436e221df GUI: Add silent mode configuration option. (#871)
When enabled, this option disables *all* GUI notifications from Santa. This is intended for kiosk-style machines where it is not expected for users to _ever_ execute unknown binaries.

Fixes #862
2022-08-11 09:17:07 -04:00
Russell Hancox
deccc8a148 GUI: For App Store published apps, include team ID. (#872)
With this change, the publisher field for an App Store published app will be  instead of

Fixes #758
2022-08-11 08:15:42 -04:00
Henry S
06da796a4d Docs: add link to GitHub (#868) 2022-08-08 16:38:34 -04:00
Russell Hancox
7b99a76d0d Docs: Add StaticRules to example mobileconfig (#866) 2022-08-03 10:59:18 -04:00
Pete Markowsky
c2d3e99446 Sync Protocol Docs (#860)
Initial commit of sync protocol docs.
2022-07-28 17:27:43 -04:00
Russell Hancox
6db7fea8ae syncservice: Add tests for NSData+Zlib and Postflight (#864) 2022-07-26 13:05:35 -04:00
Kathryn Hancox
6fcb4cfe63 Docs: Add recommended rollout doc (#861) 2022-07-22 13:50:25 -04:00
bfreezy
8b55ee4da5 santad: only allow root read+write permissions on sync-state.plist (#858) 2022-07-18 13:32:08 -04:00
Russell Hancox
cc3177502c Tests: Fix un-needed expectation in SNTExecutionControllerTest.allEventUpload (#857) 2022-07-15 18:03:34 -04:00
Kathryn Hancox
a49a59b109 Docs: Add sync server list (#856) 2022-07-15 16:19:17 -04:00
Kathryn Hancox
2c06c39c82 Added quick getting started page for deployments (#855) 2022-07-15 15:23:33 -04:00
Pete Markowsky
234f81ea7c Ensure KVO works for USB config options (#853)
Ensure KVO works for USB config options.
2022-07-15 15:13:55 -04:00
Russell Hancox
743c567bf8 santad: Log team ID in execution logs, where available (#850) 2022-07-15 12:41:56 -04:00
Russell Hancox
21220f1499 santad: Add DisableUnknownEventUpload option. (#852) 2022-07-15 12:30:20 -04:00
Russell Hancox
39f3ffe8fc santactl/status: Fix printing of static rules (#848) 2022-07-15 11:53:38 -04:00
Russell Hancox
fdb01928a0 santad: Fix re-establishment of syncservice connection (#849)
* santad: Fix re-establishment of syncservice connection

The previous version could lead to santad having lots of threads stuck waiting for connections
2022-07-15 11:53:17 -04:00
Russell Hancox
fbefbc5910 santasyncservice: Keep XSRF token in memory, don't send to daemon (#851) 2022-07-15 11:52:43 -04:00
Russell Hancox
9db00d143d santad: Improve caching of static rules (#847)
In #846 I forgot that  is only a count of the entries so if the config changes but the number of rules remains the same we would never update the cache. This PR moves the processing of the raw config into the KVO handler code so it is not at all in the hot-path.
2022-07-14 10:50:30 -04:00
Russell Hancox
1cc40d59d8 santad: Allow configuring a static set of rules via configuration profile (#846) 2022-07-13 17:58:13 -04:00
Russell Hancox
ba1ace56f0 Project: Delete tulsiproj, add basic doc about hedron (#845) 2022-07-12 13:53:57 -04:00
Russell Hancox
6d911e9d6e CI: Make CI workflow only run on source changes (#843) 2022-07-08 16:03:30 -04:00
Kathryn Hancox
7e2b291122 Docs: Updated home page with README files & nav changes (#841) 2022-07-08 15:53:16 -04:00
Tom Burgin
64096f5d08 adhoc build and run santa (#840)
* adhoc build and run santa

* fold ci into adhoc

* review updates

Co-authored-by: Tom Burgin <bur@chromium.org>
2022-07-07 17:09:53 -04:00
Matt W
aec1c74fab Use the message copy in the dispatch blocks (#839) 2022-07-06 21:51:02 -04:00
Russell Hancox
d4a0d77cb9 Docs: Add gemfile for running jekyll locally. (#834)
This lets us test docs site changes by running `bundle exec jekyll serve` from inside the docs folder.
2022-07-01 11:06:16 -04:00
Russell Hancox
7df209ed3f Project: Upgrade bazel rules_apple to 1.0.1 release (#830) 2022-06-28 14:23:47 -04:00
np5
b7421e4499 Add team ID to synced events (#827) 2022-06-24 20:00:55 +00:00
Eric Case
e044fe3601 Readme: http -> https link (#829) 2022-06-24 14:34:32 -04:00
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
307 changed files with 15336 additions and 10469 deletions

View File

@@ -0,0 +1,18 @@
# Ignore reason: These crafted binaries are used in tests
ignorePaths:
- Source/common/testdata/bad_pagezero
- Source/common/testdata/missing_pagezero
- Source/common/testdata/missing_pagezero
- Source/common/testdata/missing_pagezero
- Source/common/testdata/32bitplist
- Source/common/testdata/BundleExample.app/Contents/MacOS/BundleExample
- Source/common/testdata/DirectoryBundle/Contents/MacOS/DirectoryBundle
- Source/common/testdata/DirectoryBundle/Contents/Resources/BundleExample.app/Contents/MacOS/BundleExample
- Source/santad/testdata/binaryrules/badbinary
- Source/santad/testdata/binaryrules/goodbinary
- Source/santad/testdata/binaryrules/badcert
- Source/santad/testdata/binaryrules/banned_teamid_allowed_binary
- Source/santad/testdata/binaryrules/banned_teamid
- Source/santad/testdata/binaryrules/goodcert
- Source/santad/testdata/binaryrules/noop
- Source/santad/testdata/binaryrules/rules.db

View File

@@ -3,3 +3,14 @@ build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
build --copt=-Werror
build --copt=-Wall
build --copt=-Wno-error=deprecated-declarations
build --per_file_copt=.*\.mm\$@-std=c++17
build --cxxopt=-std=c++17
build:asan --strip=never
build:asan --copt="-Wno-macro-redefined"
build:asan --copt="-D_FORTIFY_SOURCE=0"
build:asan --copt="-O1"
build:asan --copt="-fno-omit-frame-pointer"
build:asan --copt="-fsanitize=address"
build:asan --copt="-DADDRESS_SANITIZER"
build:asan --linkopt="-fsanitize=address"

1
.bazelversion Normal file
View File

@@ -0,0 +1 @@
5.3.0

View File

@@ -1,13 +1,14 @@
name: Check Markdown links
name: Check Markdown
on:
on:
pull_request:
paths:
- "**.md"
jobs:
markdown-link-check:
markdown-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1
- run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"

View File

@@ -1,62 +1,20 @@
name: CI
on:
push:
branches:
- '*'
paths:
- 'Source/**'
pull_request:
branches:
- main
paths:
- 'Source/**'
jobs:
preqs:
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
- name: Check If We Need to Run Build/Test
id: step1
run: |
git remote add mainline https://github.com/google/santa.git
git fetch mainline main
git diff --name-only mainline/main HEAD > files.txt
echo "FILES CHANGED: $(wc -l ./files.txt)\n"
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/* || $file = Source/common/* ]]; then
build_driver=1;
break;
fi
fi
done
if [[ $build_and_run_test != 0 ]]; then
echo "NEED TO RUN BUILD AND TESTS"
echo "::set-output name=run_build_and_tests::true"
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
@@ -66,45 +24,26 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
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: Build Userspace
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
build_driver:
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
runs-on: ${{ matrix.os }}
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
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=adhoc
unit_tests:
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
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=adhoc --test_output=errors
test_coverage:
runs-on: macos-11
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Generate test coverage
@@ -113,14 +52,5 @@ 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

View File

@@ -10,4 +10,4 @@ jobs:
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
run: bazel test --test_strategy=exclusive --test_output=errors --runs_per_test 50 -t- :unit_tests --define=SANTA_BUILD_TYPE=adhoc

94
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
@@ -22,10 +27,11 @@ config_setting(
visibility = [":santa_package_group"],
)
# Used to detect CI builds
# Adhoc signed - provisioning profiles are not used.
# Used for CI runs and dev builds when SIP is disabled.
config_setting(
name = "ci_build",
values = {"define": "SANTA_BUILD_TYPE=ci"},
name = "adhoc_build",
values = {"define": "SANTA_BUILD_TYPE=adhoc"},
visibility = [":santa_package_group"],
)
@@ -49,7 +55,7 @@ run_command(
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 launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
sudo kextunload -b com.google.santa-driver 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
""",
)
@@ -60,6 +66,7 @@ run_command(
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,17 +74,14 @@ launchctl load /Library/LaunchAgents/com.google.santa.plist
run_command(
name = "reload",
srcs = [
"//Source/santa:Santa",
"//Source/santa_driver",
"//Source/gui:Santa",
],
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
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/gui/Santa.zip >/dev/null
echo "You may be asked for your password for sudo"
sudo BINARIES=/tmp/bazel_santa_reload CONF=$${BUILD_WORKSPACE_DIRECTORY}/Conf \
$${BUILD_WORKSPACE_DIRECTORY}/Conf/install.sh
@@ -92,11 +96,12 @@ echo "Time to stop being naughty"
genrule(
name = "release",
srcs = [
"//Source/santa:Santa",
"//Source/gui:Santa",
"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.newsyslog.conf",
@@ -106,7 +111,7 @@ genrule(
"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."
@@ -148,6 +153,10 @@ genrule(
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
@@ -178,69 +187,14 @@ 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:unit_tests",
"//Source/gui: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

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<installer-gui-script minSpecVersion="1">
<title>Santa</title>
<options customize="never" allow-external-scripts="no"/>
<options customize="never" allow-external-scripts="no" hostArchitectures="x86_64,arm64" />
<choices-outline>
<line choice="default" />
@@ -9,10 +9,8 @@
<choice id="default">
<pkg-ref id="com.google.santa"/>
<pkg-ref id="com.google.santa-driver"/>
</choice>
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
<pkg-ref id="com.google.santa-driver" active="system.compareVersions(my.target.systemVersion.ProductVersion, '10.15') &lt; 0">kext.pkg</pkg-ref>
</installer-gui-script>

View File

@@ -44,29 +44,28 @@ function die {
}
readonly INPUT_APP="${RELEASE_ROOT}/binaries/Santa.app"
readonly INPUT_KEXT="${RELEASE_ROOT}/binaries/santa-driver.kext"
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" CFBundleVersion)"
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 KEXT_PKG_ROOT="${SCRATCH}/kext_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}" "${KEXT_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
/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_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
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"
@@ -85,7 +84,7 @@ for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INP
done
# Notarize all the bundles
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
echo "zipping ${BN}"
@@ -96,8 +95,8 @@ for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
done
# Staple the App and Kext.
for ARTIFACT in "${INPUT_APP}" "${INPUT_KEXT}"; do
# Staple the App.
for ARTIFACT in "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
echo "stapling ${BN}"
@@ -115,11 +114,11 @@ echo "verifying signatures"
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
echo "creating fresh release tarball"
/bin/mkdir -p "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/binaries" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/conf" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/dsym" "${RELEASE_ROOT}/${RELEASE_NAME}"
/usr/bin/tar -C "${RELEASE_ROOT}" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create 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" \
@@ -132,6 +131,7 @@ echo "creating app pkg"
/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}/"
@@ -153,22 +153,6 @@ echo "creating app pkg"
--scripts "${APP_PKG_SCRIPTS}" \
"${SCRATCH}/app.pkg"
echo "creating kext pkg"
/bin/mkdir -p "${KEXT_PKG_ROOT}/Library/Extensions"
/bin/cp -vXR "${RELEASE_ROOT}/binaries/santa-driver.kext" "${KEXT_PKG_ROOT}/Library/Extensions/"
/usr/bin/pkgbuild --analyze --root "${KEXT_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 kext package
/usr/bin/pkgbuild --identifier "com.google.santa-driver" \
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
--root "${KEXT_PKG_ROOT}" \
--component-plist "${SCRATCH}/component.plist" \
"${SCRATCH}/kext.pkg"
# Build signed distribution package
echo "productbuild pkg"
/bin/mkdir -p "${SCRATCH}/${RELEASE_NAME}"

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
@@ -27,6 +26,9 @@ mkdir -p /usr/local/bin
# 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

@@ -9,6 +9,7 @@
/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

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

View File

@@ -27,6 +27,9 @@ fi
# 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
@@ -53,16 +56,13 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/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.newsyslog.conf /etc/newsyslog.d/
@@ -78,6 +78,9 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# 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)
@@ -25,6 +29,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/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

@@ -1,15 +1,14 @@
# 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)
# Santa [![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml)
<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/gui/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 system or
kernel extension (depending on the macOS version) 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.
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.
@@ -17,7 +16,7 @@ It is named Santa because it keeps track of binaries that are naughty or nice.
The Santa docs are stored in the
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
at http://santa.dev.
at https://santa.dev.
The docs include deployment options, details on how parts of Santa work and
instructions for developing Santa itself.

View File

@@ -1,7 +1,7 @@
# Reporting a Vulnerability
If you believe you have found a security vulnerability, we would appreciate private disclosure
so that we can work on a fix before disclosure. Any vulnerabilities reported to us will be
so that we can work on a fix before disclosure. Any vulnerabilities reported to us will be
disclosed publicly either when a new version with fixes is released or 90 days has passed,
whichever comes first.

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",
],
)
@@ -46,9 +68,18 @@ objc_library(
name = "SNTCachedDecision",
srcs = ["SNTCachedDecision.m"],
hdrs = ["SNTCachedDecision.h"],
deps = [
":SNTCommon",
":SNTCommonEnums",
],
)
objc_library(
name = "SNTDeviceEvent",
srcs = ["SNTDeviceEvent.m"],
hdrs = ["SNTDeviceEvent.h"],
deps = [
":SNTCommonEnums",
":SNTKernelCommon",
],
)
@@ -63,11 +94,29 @@ objc_library(
hdrs = ["SNTConfigurator.h"],
deps = [
":SNTCommonEnums",
":SNTRule",
":SNTStrengthify",
":SNTSystemInfo",
],
)
objc_library(
name = "SNTKVOManager",
srcs = ["SNTKVOManager.mm"],
hdrs = ["SNTKVOManager.h"],
deps = [
":SNTLogging",
],
)
santa_unit_test(
name = "SNTKVOManagerTest",
srcs = ["SNTKVOManagerTest.mm"],
deps = [
":SNTKVOManager",
],
)
objc_library(
name = "SNTDropRootPrivs",
srcs = ["SNTDropRootPrivs.m"],
@@ -79,34 +128,21 @@ objc_library(
srcs = ["SNTFileInfo.m"],
hdrs = ["SNTFileInfo.h"],
deps = [
":SNTLogging",
"@FMDB",
"@MOLCodesignChecker",
],
)
cc_library(
name = "SNTKernelCommon",
hdrs = ["SNTKernelCommon.h"],
name = "SNTCommon",
hdrs = ["SNTCommon.h"],
defines = [
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
)
cc_library(
name = "SNTLoggingKernel",
hdrs = ["SNTLogging.h"],
copts = [
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = [
"KERNEL",
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
)
objc_library(
name = "SNTLogging",
srcs = ["SNTLogging.m"],
@@ -122,28 +158,20 @@ 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",
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
deps = [":SNTLoggingKernel"],
)
objc_library(
name = "SNTRule",
srcs = ["SNTRule.m"],
hdrs = ["SNTRule.h"],
deps = [":SNTCommonEnums"],
deps = [
":SNTCommonEnums",
":SNTSyncConstants",
],
)
santa_unit_test(
name = "SNTRuleTest",
srcs = ["SNTRuleTest.m"],
deps = [":SNTRule"],
)
objc_library(
@@ -161,6 +189,12 @@ cc_library(
hdrs = ["SNTStrengthify.h"],
)
objc_library(
name = "SNTSyncConstants",
srcs = ["SNTSyncConstants.m"],
hdrs = ["SNTSyncConstants.h"],
)
objc_library(
name = "SNTSystemInfo",
srcs = ["SNTSystemInfo.m"],
@@ -191,10 +225,17 @@ objc_library(
name = "SNTXPCControlInterface",
srcs = ["SNTXPCControlInterface.m"],
hdrs = ["SNTXPCControlInterface.h"],
defines = select({
"//:adhoc_build": ["SANTAADHOC"],
"//conditions:default": None,
}),
deps = [
":SNTCommonEnums",
":SNTConfigurator",
":SNTRule",
":SNTStoredEvent",
":SNTXPCUnprivilegedControlInterface",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
@@ -216,21 +257,12 @@ objc_library(
deps = [":SNTCommonEnums"],
)
objc_library(
name = "SNTXPCSyncdInterface",
srcs = ["SNTXPCSyncdInterface.m"],
hdrs = ["SNTXPCSyncdInterface.h"],
deps = [
":SNTCommonEnums",
":SNTStoredEvent",
],
)
objc_library(
name = "SNTXPCSyncServiceInterface",
srcs = ["SNTXPCSyncServiceInterface.m"],
hdrs = ["SNTXPCSyncServiceInterface.h"],
deps = [
":SNTCommonEnums",
":SNTStoredEvent",
"@MOLXPCConnection",
],
@@ -241,8 +273,8 @@ objc_library(
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
deps = [
":SNTCommon",
":SNTCommonEnums",
":SNTKernelCommon",
":SNTRule",
":SNTStoredEvent",
":SNTXPCBundleServiceInterface",
@@ -278,13 +310,40 @@ santa_unit_test(
deps = [":SNTMetricSet"],
)
santa_unit_test(
name = "SNTCachedDecisionTest",
srcs = ["SNTCachedDecisionTest.mm"],
deps = [
"//Source/common:SNTCachedDecision",
"//Source/common:TestUtils",
"@OCMock",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTCachedDecisionTest",
":SNTFileInfoTest",
":SNTKVOManagerTest",
":SNTMetricSetTest",
":SNTPrefixTreeTest",
":SNTRuleTest",
":SantaCacheTest",
],
visibility = ["//:santa_package_group"],
)
objc_library(
name = "TestUtils",
testonly = 1,
srcs = ["TestUtils.mm"],
hdrs = ["TestUtils.h"],
sdk_dylibs = [
"bsm",
],
deps = [
"@OCMock",
"@com_google_googletest//:gtest",
],
)

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

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -12,10 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <EndpointSecurity/EndpointSecurity.h>
#import <Foundation/Foundation.h>
#import "Source/common/SNTCommon.h"
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
@class MOLCertificate;
@@ -24,6 +25,8 @@
///
@interface SNTCachedDecision : NSObject
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile;
@property santa_vnode_id_t vnodeId;
@property SNTEventState decision;
@property NSString *decisionExtra;

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -15,4 +15,14 @@
#import "Source/common/SNTCachedDecision.h"
@implementation SNTCachedDecision
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile {
self = [super init];
if (self) {
_vnodeId.fsid = (uint64_t)esFile->stat.st_dev;
_vnodeId.fileid = esFile->stat.st_ino;
}
return self;
}
@end

View File

@@ -0,0 +1,36 @@
/// 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 <XCTest/XCTest.h>
#import "Source/common/SNTCachedDecision.h"
#include "Source/common/TestUtils.h"
@interface SNTCachedDecisionTest : XCTestCase
@end
@implementation SNTCachedDecisionTest
- (void)testSNTCachedDecisionInit {
// Ensure the vnodeId field is properly set from the es_file_t
struct stat sb = MakeStat(1234, 5678);
es_file_t file = MakeESFile("foo", sb);
SNTCachedDecision *cd = [[SNTCachedDecision alloc] initWithEndpointSecurityFile:&file];
XCTAssertEqual(sb.st_ino, cd.vnodeId.fileid);
XCTAssertEqual(sb.st_dev, cd.vnodeId.fsid);
}
@end

59
Source/common/SNTCommon.h Normal file
View File

@@ -0,0 +1,59 @@
/// Copyright 2015-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.
///
/// Common defines between daemon <-> client
///
#ifndef SANTA__COMMON__COMMON_H
#define SANTA__COMMON__COMMON_H
#include <stdint.h>
#include <sys/param.h>
// Branch prediction
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
typedef enum {
ACTION_UNSET,
// REQUESTS
// If an operation is awaiting a cache decision from a similar operation
// currently being processed, it will poll about every 5 ms for an answer.
ACTION_REQUEST_BINARY,
// RESPONSES
ACTION_RESPOND_ALLOW,
ACTION_RESPOND_DENY,
ACTION_RESPOND_ALLOW_COMPILER,
} santa_action_t;
#define RESPONSE_VALID(x) \
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY || \
x == ACTION_RESPOND_ALLOW_COMPILER)
// Struct to manage vnode IDs
typedef struct santa_vnode_id_t {
uint64_t fsid;
uint64_t fileid;
#ifdef __cplusplus
bool operator==(const santa_vnode_id_t &rhs) const {
return fsid == rhs.fsid && fileid == rhs.fileid;
}
#endif
} santa_vnode_id_t;
#endif // SANTA__COMMON__COMMON_H

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -57,6 +57,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
SNTEventStateBlockTeamID = 1 << 20,
SNTEventStateBlockLongPath = 1 << 21,
// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
@@ -92,6 +93,23 @@ 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) {
@@ -100,9 +118,7 @@ typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
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";
static const char *kSantaCtlPath = "/Applications/Santa.app/Contents/MacOS/santactl";
static const char *kSantaAppPath = "/Applications/Santa.app";

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -16,6 +16,8 @@
#import "Source/common/SNTCommonEnums.h"
@class SNTRule;
///
/// Singleton that provides an interface for managing configuration values on disk
/// @note This class is designed as a singleton but that is not strictly enforced.
@@ -26,7 +28,7 @@
#pragma mark - Daemon Settings
///
/// The operating mode.
/// The operating mode. Defaults to MONITOR.
///
@property(readonly, nonatomic) SNTClientMode clientMode;
@@ -35,6 +37,44 @@
///
- (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;
///
/// A set of static rules that should always apply. These can be used as a
/// fallback set of rules for management tools that should always be allowed to
/// run even if a sync server does something unexpected. It can also be used
/// as the sole source of rules, distributed with an MDM.
///
/// The value of this key should be an array containing dictionaries. Each
/// dictionary should contain the same keys used for syncing, e.g:
///
/// <key>StaticRules</key>
/// <array>
/// <dict>
/// <key>identifier</key>
/// <string>binary sha256, certificate sha256, team ID</string>
/// <key>rule_type</key>
/// <string>BINARY</string> (one of BINARY, CERTIFICATE or TEAMID)
/// <key>policy</key>
/// <string>BLOCKLIST</string> (one of ALLOWLIST, ALLOWLIST_COMPILER, BLOCKLIST,
/// SILENT_BLOCKLIST)
/// </dict>
/// </array>
///
/// The return of this property is a dictionary where the keys are the
/// identifiers of each rule, with the SNTRule as a value
///
@property(readonly, nonatomic) NSDictionary<NSString *, SNTRule *> *staticRules;
///
/// The regex of allowed paths. Regexes are specified in ICU format.
///
@@ -140,8 +180,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 +202,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,27 +245,18 @@
///
@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
/// performance, particularly when Santa is run alongside other system
/// extensions.
/// Has no effect if the system extension is not being used. Defaults to NO.
///
@property(readonly, nonatomic) BOOL enableSysxCache;
#pragma mark - GUI Settings
///
/// When silent mode is enabled, Santa will never show notifications for
/// blocked processes.
///
/// This can be a very confusing experience for users, use with caution.
///
/// Defaults to NO.
///
@property(readonly, nonatomic) BOOL enableSilentMode;
///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
@@ -235,6 +307,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"
@@ -356,6 +442,22 @@
///
@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, events will *not* be uploaded for ALLOW_UNKNOWN events for clients in Monitor mode.
///
@property(nonatomic) BOOL disableUnknownEventUpload;
///
/// If true, forks and exits will be logged. Defaults to false.
///
@@ -427,6 +529,11 @@
///
@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

@@ -1,4 +1,4 @@
/// Copyright 2021 Google Inc. All rights reserved.
/// Copyright 2014-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.
@@ -16,6 +16,7 @@
#include <sys/stat.h>
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTSystemInfo.h"
@@ -28,11 +29,15 @@
@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?
@property(readonly, nonatomic) BOOL debugFlag;
/// Holds the last processed hash of the static rules list.
@property(atomic) NSDictionary *cachedStaticRules;
@end
@implementation SNTConfigurator
@@ -44,8 +49,10 @@ NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The keys managed by a mobileconfig.
static NSString *const kStaticRules = @"StaticRules";
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";
@@ -60,12 +67,16 @@ static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kAboutText = @"AboutText";
static NSString *const kEnableSilentModeKey = @"EnableSilentMode";
static NSString *const kAboutTextKey = @"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";
@@ -77,12 +88,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";
static NSString *const kIgnoreOtherEndpointSecurityClients = @"IgnoreOtherEndpointSecurityClients";
static NSString *const kEnableDebugLogging = @"EnableDebugLogging";
@@ -96,6 +108,7 @@ 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";
@@ -104,11 +117,14 @@ 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";
static NSString *const kDisableUnknownEventUploadKey = @"DisableUnknownEventUpload";
// 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.
@@ -138,10 +154,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kRemountUSBModeKey : array,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
kSyncCleanRequired : number,
kEnableAllEventUploadKey : number,
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
kFailClosedKey : number,
kEnableTransitiveRulesKey : number,
kEnableTransitiveRulesKeyDeprecated : number,
kFileChangesRegexKey : re,
@@ -154,14 +172,18 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kRemountUSBModeKey : array,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kAboutText : string,
kEnableSilentModeKey : string,
kAboutTextKey : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
kUnknownBlockMessage : string,
kBannedBlockMessage : string,
kBannedUSBBlockMessage : string,
kRemountUSBBlockMessage : string,
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kStaticRules : array,
kSyncBaseURLKey : string,
kSyncProxyConfigKey : dictionary,
kClientAuthCertificateFileKey : string,
@@ -178,9 +200,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,
kEnableDebugLogging : number,
@@ -191,11 +215,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kMetricFormat : string,
kMetricURL : string,
kMetricExportInterval : number,
kMetricExportTimeout : number,
kMetricExtraLabels : dictionary,
kEnableAllEventUploadKey : number,
kDisableUnknownEventUploadKey : number,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
_configState = [self readForcedConfig];
[self cacheStaticRules];
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
_debugFlag = [[NSProcessInfo processInfo].arguments containsObject:@"--debug"];
[self startWatchingDefaults];
@@ -263,6 +291,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
return [self configStateSet];
}
@@ -271,6 +303,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSilentMode {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingAboutText {
return [self configStateSet];
}
@@ -355,6 +391,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];
}
@@ -363,12 +415,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
return [self configStateSet];
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
return [self configStateSet];
+ (NSSet *)keyPathsForValuesAffectingDisableUnknownEventUpload {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableForkAndExitLogging {
@@ -407,6 +459,26 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingBlockUSBMount {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingBannedUSBBlockMessage {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingRemountUSBMode {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingRemountUSBBlockMessage {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingUsbBlockMessage {
return [self syncAndConfigStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -429,6 +501,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];
@@ -508,6 +586,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return args;
}
- (NSDictionary<NSString *, SNTRule *> *)staticRules {
return self.cachedStaticRules;
}
- (NSURL *)syncBaseURL {
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
@@ -529,8 +611,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : NO;
}
- (BOOL)enableSilentMode {
NSNumber *number = self.configState[kEnableSilentModeKey];
return number ? [number boolValue] : NO;
}
- (NSString *)aboutText {
return self.configState[kAboutText];
return self.configState[kAboutTextKey];
}
- (NSURL *)moreInfoURL {
@@ -553,6 +640,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];
}
@@ -640,33 +742,76 @@ 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)enableCleanSyncEventUpload {
NSNumber *number = self.configState[kSyncEnableCleanSyncEventUpload];
return number ? [number boolValue] : NO;
}
- (BOOL)enableSysxCache {
NSNumber *number = self.configState[kEnableSysxCache];
return number ? [number boolValue] : YES;
- (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)disableUnknownEventUpload {
NSNumber *n = self.syncState[kDisableUnknownEventUploadKey];
if (n) return [n boolValue];
return [self.configState[kDisableUnknownEventUploadKey] boolValue];
}
- (void)setDisableUnknownEventUpload:(BOOL)enabled {
[self updateSyncStateForKey:kDisableUnknownEventUploadKey value:@(enabled)];
}
- (BOOL)enableForkAndExitLogging {
@@ -751,6 +896,16 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
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];
}
@@ -804,7 +959,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
syncState[kAllowedPathRegexKey] = [syncState[kAllowedPathRegexKey] pattern];
syncState[kBlockedPathRegexKey] = [syncState[kBlockedPathRegexKey] pattern];
[syncState writeToFile:kSyncStateFilePath atomically:YES];
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0644}
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0600}
ofItemAtPath:kSyncStateFilePath
error:NULL];
}
@@ -861,6 +1016,24 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
///
- (void)handleChange {
self.configState = [self readForcedConfig];
[self cacheStaticRules];
}
///
/// Processes the StaticRules key to create SNTRule objects and caches them for quick use
///
- (void)cacheStaticRules {
NSArray *staticRules = self.configState[kStaticRules];
if (![staticRules isKindOfClass:[NSArray class]]) return;
NSMutableDictionary<NSString *, SNTRule *> *rules =
[NSMutableDictionary dictionaryWithCapacity:staticRules.count];
for (id rule in staticRules) {
if (![rule isKindOfClass:[NSDictionary class]]) return;
SNTRule *r = [[SNTRule alloc] initWithDictionary:rule];
rules[r.identifier] = r;
}
self.cachedStaticRules = [rules copy];
}
@end

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// 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.
@@ -14,14 +14,14 @@
#import <Foundation/Foundation.h>
///
/// The main controller class for santad
///
@interface SNTApplication : NSObject
@interface SNTDeviceEvent : NSObject <NSSecureCoding>
///
/// Begins fielding requests from the driver
///
- (void)start;
- (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

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -12,6 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <EndpointSecurity/EndpointSecurity.h>
#import <Foundation/Foundation.h>
@class MOLCodesignChecker;
@@ -32,6 +33,14 @@
///
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error;
///
/// Convenience initializer.
///
/// @param esFile Pointer to an es_file_t provided by the EndpointSecurity framework.
/// Assumes that the path is a resolved path.
///
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile error:(NSError **)error;
///
/// Convenience initializer.
///

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -25,6 +25,8 @@
#include <sys/stat.h>
#include <sys/xattr.h>
#import "Source/common/SNTLogging.h"
// Simple class to hold the data of a mach_header and the offset within the file
// in which that header was found.
@interface MachHeaderWithOffset : NSObject
@@ -48,6 +50,7 @@
@property NSFileHandle *fileHandle;
@property NSUInteger fileSize;
@property NSString *fileOwnerHomeDir;
@property NSString *sha256Storage;
// Cached properties
@property NSBundle *bundleRef;
@@ -63,6 +66,26 @@
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
struct stat fileStat;
if (path.length) {
lstat(path.UTF8String, &fileStat);
}
return [self initWithResolvedPath:path stat:&fileStat error:error];
}
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile error:(NSError **)error {
return [self initWithResolvedPath:@(esFile->path.data) stat:&esFile->stat error:error];
}
- (instancetype)initWithResolvedPath:(NSString *)path
stat:(const struct stat *)fileStat
error:(NSError **)error {
if (!fileStat) {
// This is a programming error. Bail.
LOGE(@"NULL stat buffer unsupported");
exit(EXIT_FAILURE);
}
self = [super init];
if (self) {
_path = path;
@@ -76,9 +99,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return nil;
}
struct stat fileStat;
lstat(_path.UTF8String, &fileStat);
if (!((S_IFMT & fileStat.st_mode) == S_IFREG)) {
if (!((S_IFMT & fileStat->st_mode) == S_IFREG)) {
if (error) {
NSString *errStr = [NSString stringWithFormat:@"Non regular file: %s", strerror(errno)];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
@@ -88,12 +109,12 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return nil;
}
_fileSize = fileStat.st_size;
_fileSize = fileStat->st_size;
if (_fileSize == 0) return nil;
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (fileStat->st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat->st_uid);
if (pwd) {
_fileOwnerHomeDir = @(pwd->pw_dir);
}
@@ -214,9 +235,13 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
}
- (NSString *)SHA256 {
NSString *sha256;
[self hashSHA1:NULL SHA256:&sha256];
return sha256;
// Memoize the value
if (!self.sha256Storage) {
NSString *sha256;
[self hashSHA1:NULL SHA256:&sha256];
self.sha256Storage = sha256;
}
return self.sha256Storage;
}
#pragma mark File Type Info

View File

@@ -0,0 +1,34 @@
/// Copyright 2022 Google LLC
///
/// 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
///
/// https://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>
// The callback type when KVO notifications are received for observed key paths.
// The first parameter is the previous value, the second paramter is the new value.
typedef void (^KVOCallback)(id oldValue, id newValue);
@interface SNTKVOManager : NSObject
// Add an observer for the selector on the given object. When a KVO notification
// is received, the callback is called. If the notification contains objects that
// are not of the expectedType, nil is passed as the argument to the callback.
// The observer is removed when the returned instance is deallocated.
- (instancetype)initWithObject:(id)object
selector:(SEL)selector
type:(Class)expectedType
callback:(KVOCallback)callback;
- (instancetype)init NS_UNAVAILABLE;
@end

View File

@@ -0,0 +1,72 @@
/// Copyright 2022 Google LLC
///
/// 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
///
/// https://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/SNTKVOManager.h"
#import "Source/common/SNTLogging.h"
@interface SNTKVOManager ()
@property KVOCallback callback;
@property Class expectedType;
@property NSString *keyPath;
@property id object;
@end
@implementation SNTKVOManager
- (instancetype)initWithObject:(id)object
selector:(SEL)selector
type:(Class)expectedType
callback:(KVOCallback)callback {
self = [super self];
if (self) {
NSString *selectorName = NSStringFromSelector(selector);
if (![object respondsToSelector:selector]) {
LOGE(@"Attempt to add observer for an unknown selector (%@) for object (%@)", selectorName,
[object class]);
return nil;
}
_object = object;
_keyPath = selectorName;
_expectedType = expectedType;
_callback = callback;
[object addObserver:self
forKeyPath:selectorName
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
}
return self;
}
- (void)dealloc {
[self.object removeObserver:self forKeyPath:self.keyPath context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *, id> *)change
context:(void *)context {
id oldValue = [change[NSKeyValueChangeOldKey] isKindOfClass:self.expectedType]
? change[NSKeyValueChangeOldKey]
: nil;
id newValue = [change[NSKeyValueChangeNewKey] isKindOfClass:self.expectedType]
? change[NSKeyValueChangeNewKey]
: nil;
self.callback(oldValue, newValue);
}
@end

View File

@@ -0,0 +1,129 @@
/// Copyright 2022 Google LLC
///
/// 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
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <XCTest/XCTest.h>
#import "Source/common/SNTKVOManager.h"
@interface Foo : NSObject
@property NSNumber *propNumber;
@property NSArray *propArray;
@property id propId;
@end
@implementation Foo
@end
@interface SNTKVOManagerTest : XCTestCase
@end
@implementation SNTKVOManagerTest
- (void)testInvalidSelector {
Foo *foo = [[Foo alloc] init];
SNTKVOManager *kvo = [[SNTKVOManager alloc] initWithObject:foo
selector:NSSelectorFromString(@"doesNotExist")
type:[NSNumber class]
callback:^(id, id){
}];
XCTAssertNil(kvo);
}
- (void)testNormalOperation {
Foo *foo = [[Foo alloc] init];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
int origVal = 123;
int update1 = 456;
int update2 = 789;
foo.propNumber = @(origVal);
// Store the values from the callback to test against expected values
__block int oldVal;
__block int newVal;
SNTKVOManager *kvo =
[[SNTKVOManager alloc] initWithObject:foo
selector:@selector(propNumber)
type:[NSNumber class]
callback:^(NSNumber *oldValue, NSNumber *newValue) {
oldVal = [oldValue intValue];
newVal = [newValue intValue];
dispatch_semaphore_signal(sema);
}];
XCTAssertNotNil(kvo);
// Ensure an update to the observed property triggers the callback
foo.propNumber = @(update1);
XCTAssertEqual(0,
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
"Failed waiting for first observable update");
XCTAssertEqual(oldVal, origVal);
XCTAssertEqual(newVal, update1);
// One more time why not
foo.propNumber = @(update2);
XCTAssertEqual(0,
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
"Failed waiting for second observable update");
XCTAssertEqual(oldVal, update1);
XCTAssertEqual(newVal, update2);
}
- (void)testUnexpectedTypes {
Foo *foo = [[Foo alloc] init];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSString *origVal = @"any_val";
NSString *update = @"new_val";
foo.propId = origVal;
__block id oldVal;
__block id newVal;
SNTKVOManager *kvo = [[SNTKVOManager alloc] initWithObject:foo
selector:@selector(propId)
type:[NSString class]
callback:^(id oldValue, id newValue) {
oldVal = oldValue;
newVal = newValue;
dispatch_semaphore_signal(sema);
}];
XCTAssertNotNil(kvo);
// Update to an unexpected type (here, NSNumber instead of NSString)
foo.propId = @(123);
XCTAssertEqual(0,
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
"Failed waiting for first observable update");
XCTAssertEqualObjects(oldVal, origVal);
XCTAssertNil(newVal);
// Update again with an expected type, ensure oldVal is now nil
foo.propId = update;
XCTAssertEqual(0,
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
"Failed waiting for first observable update");
XCTAssertNil(oldVal);
XCTAssertEqualObjects(newVal, update);
}
@end

View File

@@ -1,146 +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.
///
/// Common defines between kernel <-> userspace
///
#ifndef SANTA__COMMON__KERNELCOMMON_H
#define SANTA__COMMON__KERNELCOMMON_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,
// REQUESTS
ACTION_REQUEST_SHUTDOWN = 10,
ACTION_REQUEST_BINARY = 11,
// RESPONSES
ACTION_RESPOND_ALLOW = 20,
ACTION_RESPOND_DENY = 21,
ACTION_RESPOND_TOOLONG = 22,
ACTION_RESPOND_ACK = 23,
ACTION_RESPOND_ALLOW_COMPILER = 24,
// The following response is stored only in the kernel decision cache.
// It is removed by SNTCompilerController
ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE = 25,
// NOTIFY
ACTION_NOTIFY_EXEC = 30,
ACTION_NOTIFY_WRITE = 31,
ACTION_NOTIFY_RENAME = 32,
ACTION_NOTIFY_LINK = 33,
ACTION_NOTIFY_EXCHANGE = 34,
ACTION_NOTIFY_DELETE = 35,
ACTION_NOTIFY_WHITELIST = 36,
ACTION_NOTIFY_FORK = 37,
ACTION_NOTIFY_EXIT = 38,
// ERROR
ACTION_ERROR = 99,
} santa_action_t;
#define RESPONSE_VALID(x) \
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY || \
x == ACTION_RESPOND_ALLOW_COMPILER || \
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
// Struct to manage vnode IDs
typedef struct santa_vnode_id_t {
uint64_t fsid;
uint64_t fileid;
#ifdef __cplusplus
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;
uid_t uid;
gid_t gid;
pid_t pid;
int pidversion;
pid_t ppid;
char path[MAXPATHLEN];
char newpath[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.
void *es_message;
// For messages that originate from EndpointSecurity, this points to an
// NSArray of the 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

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

@@ -108,6 +108,11 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType);
*/
+ (instancetype)sharedInstance;
/**
* Resets all the metrics in this set. Intended only for testing.
*/
- (void)reset;
/**
* Add a root label to the MetricSet.
*/

View File

@@ -27,9 +27,9 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
default: typeStr = @"SNTMetricTypeUnknown"; break;
default: typeStr = [NSString stringWithFormat:@"SNTMetricTypeUnknown %ld", metricType]; break;
}
return [NSString stringWithFormat:@"%@ %ld", typeStr, metricType];
return typeStr;
}
/**
@@ -485,6 +485,10 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
return self;
}
- (void)reset {
_metrics = [[NSMutableDictionary alloc] init];
}
- (void)addRootLabel:(NSString *)label value:(NSString *)value {
@synchronized(self) {
_rootLabels[label] = value;

View File

@@ -45,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 {
@@ -630,39 +630,39 @@
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
@"expected" : @"SNTMetricTypeConstantBool 1"
@"expected" : @"SNTMetricTypeConstantBool"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
@"expected" : @"SNTMetricTypeConstantString 2"
@"expected" : @"SNTMetricTypeConstantString"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
@"expected" : @"SNTMetricTypeConstantInt64 3"
@"expected" : @"SNTMetricTypeConstantInt64"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
@"expected" : @"SNTMetricTypeConstantDouble 4"
@"expected" : @"SNTMetricTypeConstantDouble"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
@"expected" : @"SNTMetricTypeGaugeBool 5"
@"expected" : @"SNTMetricTypeGaugeBool"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
@"expected" : @"SNTMetricTypeGaugeString 6"
@"expected" : @"SNTMetricTypeGaugeString"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
@"expected" : @"SNTMetricTypeGaugeInt64 7"
@"expected" : @"SNTMetricTypeGaugeInt64"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
@"expected" : @"SNTMetricTypeGaugeDouble 8"
@"expected" : @"SNTMetricTypeGaugeDouble"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
@"expected" : @"SNTMetricTypeCounter 9"
@"expected" : @"SNTMetricTypeCounter"
}
];

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

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -51,18 +51,23 @@
/// Designated initializer.
///
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp;
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp;
///
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
///
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;
///
/// Initialize with a dictionary received from a sync server.
///
- (instancetype)initWithDictionary:(NSDictionary *)dict;
///
/// Sets timestamp of rule to the current time.

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "Source/common/SNTRule.h"
#import "Source/common/SNTSyncConstants.h"
@interface SNTRule ()
@property(readwrite) NSUInteger timestamp;
@@ -48,6 +49,60 @@
return self;
}
// Converts rule information downloaded from the server into a SNTRule. Because any information
// not recorded by SNTRule is thrown away here, this method is also responsible for dealing with
// the extra bundle rule information (bundle_hash & rule_count).
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) return nil;
self = [super init];
if (self) {
_identifier = dict[kRuleIdentifier];
if (![_identifier isKindOfClass:[NSString class]] || !_identifier.length) {
_identifier = dict[kRuleSHA256];
}
if (![_identifier isKindOfClass:[NSString class]] || !_identifier.length) return nil;
NSString *policyString = dict[kRulePolicy];
if (![policyString isKindOfClass:[NSString class]]) return nil;
if ([policyString isEqual:kRulePolicyAllowlist] ||
[policyString isEqual:kRulePolicyAllowlistDeprecated]) {
_state = SNTRuleStateAllow;
} else if ([policyString isEqual:kRulePolicyAllowlistCompiler] ||
[policyString isEqual:kRulePolicyAllowlistCompilerDeprecated]) {
_state = SNTRuleStateAllowCompiler;
} else if ([policyString isEqual:kRulePolicyBlocklist] ||
[policyString isEqual:kRulePolicyBlocklistDeprecated]) {
_state = SNTRuleStateBlock;
} else if ([policyString isEqual:kRulePolicySilentBlocklist] ||
[policyString isEqual:kRulePolicySilentBlocklistDeprecated]) {
_state = SNTRuleStateSilentBlock;
} else if ([policyString isEqual:kRulePolicyRemove]) {
_state = SNTRuleStateRemove;
} else {
return nil;
}
NSString *ruleTypeString = dict[kRuleType];
if (![ruleTypeString isKindOfClass:[NSString class]]) return nil;
if ([ruleTypeString isEqual:kRuleTypeBinary]) {
_type = SNTRuleTypeBinary;
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
_type = SNTRuleTypeCertificate;
} else if ([ruleTypeString isEqual:kRuleTypeTeamID]) {
_type = SNTRuleTypeTeamID;
} else {
return nil;
}
NSString *customMsg = dict[kRuleCustomMsg];
if ([customMsg isKindOfClass:[NSString class]] && customMsg.length) {
_customMsg = customMsg;
}
}
return self;
}
#pragma mark NSSecureCoding
#pragma clang diagnostic push

116
Source/common/SNTRuleTest.m Normal file
View File

@@ -0,0 +1,116 @@
/// 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 <XCTest/XCTest.h>
#import "Source/common/SNTRule.h"
@interface SNTRuleTest : XCTestCase
@end
@implementation SNTRuleTest
- (void)testInitWithDictionaryValid {
SNTRule *sut;
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"some-sort-of-identifier",
@"policy" : @"ALLOWLIST",
@"rule_type" : @"BINARY",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqual(sut.type, SNTRuleTypeBinary);
XCTAssertEqual(sut.state, SNTRuleStateAllow);
sut = [[SNTRule alloc] initWithDictionary:@{
@"sha256" : @"some-sort-of-identifier",
@"policy" : @"BLOCKLIST",
@"rule_type" : @"CERTIFICATE",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqual(sut.type, SNTRuleTypeCertificate);
XCTAssertEqual(sut.state, SNTRuleStateBlock);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"some-sort-of-identifier",
@"policy" : @"SILENT_BLOCKLIST",
@"rule_type" : @"TEAMID",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
XCTAssertEqual(sut.state, SNTRuleStateSilentBlock);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"some-sort-of-identifier",
@"policy" : @"ALLOWLIST_COMPILER",
@"rule_type" : @"BINARY",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqual(sut.type, SNTRuleTypeBinary);
XCTAssertEqual(sut.state, SNTRuleStateAllowCompiler);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"some-sort-of-identifier",
@"policy" : @"REMOVE",
@"rule_type" : @"TEAMID",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
XCTAssertEqual(sut.state, SNTRuleStateRemove);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"some-sort-of-identifier",
@"policy" : @"ALLOWLIST",
@"rule_type" : @"TEAMID",
@"custom_msg" : @"A custom block message",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
XCTAssertEqual(sut.state, SNTRuleStateAllow);
XCTAssertEqualObjects(sut.customMsg, @"A custom block message");
}
- (void)testInitWithDictionaryInvalid {
SNTRule *sut;
sut = [[SNTRule alloc] initWithDictionary:@{}];
XCTAssertNil(sut);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"an-identifier",
}];
XCTAssertNil(sut);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"an-identifier",
@"policy" : @"OTHERPOLICY",
@"rule_type" : @"BINARY",
}];
XCTAssertNil(sut);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"an-identifier",
@"policy" : @"ALLOWLIST",
@"rule_type" : @"OTHER_RULE_TYPE",
}];
XCTAssertNil(sut);
}
@end

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -95,6 +95,11 @@
///
@property NSArray *signingChain;
///
/// If the executed file was signed, this is the Team ID if present in the signature information.
///
@property NSString *teamID;
///
/// The user who executed the binary.
///

View File

@@ -49,6 +49,7 @@
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
ENCODE(self.signingChain, @"signingChain");
ENCODE(self.teamID, @"teamID");
ENCODE(self.executingUser, @"executingUser");
ENCODE(self.occurrenceDate, @"occurrenceDate");
@@ -93,6 +94,7 @@
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
_teamID = DECODE(NSString, @"teamID");
_executingUser = DECODE(NSString, @"executingUser");
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");

View File

@@ -1,4 +1,4 @@
/// Copyright 2016 Google Inc. All rights reserved.
/// Copyright 2016-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.
@@ -12,10 +12,14 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#define STRONGIFY(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong __typeof(var) var = (Weak_##var); \
// clang-format off
#define STRONGIFY(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong __typeof(var) var = (Weak_##var); \
_Pragma("clang diagnostic pop")
#define WEAKIFY(var) __weak __typeof(var) Weak_##var = (var);
// clang-format on

View File

@@ -21,6 +21,7 @@ extern NSString *const kHostname;
extern NSString *const kSantaVer;
extern NSString *const kOSVer;
extern NSString *const kOSBuild;
extern NSString *const kModelIdentifier;
extern NSString *const kPrimaryUser;
extern NSString *const kRequestCleanSync;
extern NSString *const kBatchSize;
@@ -49,6 +50,8 @@ extern NSString *const kEnableBundlesDeprecated;
extern NSString *const kEnableTransitiveRules;
extern NSString *const kEnableTransitiveRulesDeprecated;
extern NSString *const kEnableTransitiveRulesSuperDeprecated;
extern NSString *const kEnableAllEventUpload;
extern NSString *const kDisableUnknownEventUpload;
extern NSString *const kEvents;
extern NSString *const kFileSHA256;
@@ -61,10 +64,12 @@ extern NSString *const kDecisionAllowUnknown;
extern NSString *const kDecisionAllowBinary;
extern NSString *const kDecisionAllowCertificate;
extern NSString *const kDecisionAllowScope;
extern NSString *const kDecisionAllowTeamID;
extern NSString *const kDecisionBlockUnknown;
extern NSString *const kDecisionBlockBinary;
extern NSString *const kDecisionBlockCertificate;
extern NSString *const kDecisionBlockScope;
extern NSString *const kDecisionBlockTeamID;
extern NSString *const kDecisionUnknown;
extern NSString *const kDecisionBundleBinary;
extern NSString *const kLoggedInUsers;
@@ -88,6 +93,7 @@ extern NSString *const kCertOrg;
extern NSString *const kCertOU;
extern NSString *const kCertValidFrom;
extern NSString *const kCertValidUntil;
extern NSString *const kTeamID;
extern NSString *const kQuarantineDataURL;
extern NSString *const kQuarantineRefererURL;
extern NSString *const kQuarantineTimestamp;
@@ -131,5 +137,5 @@ extern const NSUInteger kDefaultEventBatchSize;
/// Are represented in seconds
///
extern const NSUInteger kDefaultFullSyncInterval;
extern const NSUInteger kDefaultFCMFullSyncInterval;
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;
extern const NSUInteger kDefaultPushNotificationsFullSyncInterval;
extern const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline;

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTSyncConstants.h"
#import "Source/common/SNTSyncConstants.h"
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
@@ -21,6 +21,7 @@ NSString *const kHostname = @"hostname";
NSString *const kSantaVer = @"santa_version";
NSString *const kOSVer = @"os_version";
NSString *const kOSBuild = @"os_build";
NSString *const kModelIdentifier = @"model_identifier";
NSString *const kPrimaryUser = @"primary_user";
NSString *const kRequestCleanSync = @"request_clean_sync";
NSString *const kBatchSize = @"batch_size";
@@ -50,6 +51,8 @@ NSString *const kEnableBundlesDeprecated = @"bundles_enabled";
NSString *const kEnableTransitiveRules = @"enable_transitive_rules";
NSString *const kEnableTransitiveRulesDeprecated = @"enabled_transitive_whitelisting";
NSString *const kEnableTransitiveRulesSuperDeprecated = @"transitive_whitelisting_enabled";
NSString *const kEnableAllEventUpload = @"enable_all_event_upload";
NSString *const kDisableUnknownEventUpload = @"disable_unknown_event_upload";
NSString *const kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -62,10 +65,12 @@ NSString *const kDecisionAllowUnknown = @"ALLOW_UNKNOWN";
NSString *const kDecisionAllowBinary = @"ALLOW_BINARY";
NSString *const kDecisionAllowCertificate = @"ALLOW_CERTIFICATE";
NSString *const kDecisionAllowScope = @"ALLOW_SCOPE";
NSString *const kDecisionAllowTeamID = @"ALLOW_TEAMID";
NSString *const kDecisionBlockUnknown = @"BLOCK_UNKNOWN";
NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
NSString *const kDecisionBlockTeamID = @"BLOCK_TEAMID";
NSString *const kDecisionUnknown = @"UNKNOWN";
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
NSString *const kLoggedInUsers = @"logged_in_users";
@@ -89,6 +94,7 @@ NSString *const kCertOrg = @"org";
NSString *const kCertOU = @"ou";
NSString *const kCertValidFrom = @"valid_from";
NSString *const kCertValidUntil = @"valid_until";
NSString *const kTeamID = @"team_id";
NSString *const kQuarantineDataURL = @"quarantine_data_url";
NSString *const kQuarantineRefererURL = @"quarantine_referer_url";
NSString *const kQuarantineTimestamp = @"quarantine_timestamp";
@@ -124,5 +130,5 @@ NSString *const kLogSync = @"log_sync";
const NSUInteger kDefaultEventBatchSize = 50;
const NSUInteger kDefaultFullSyncInterval = 600;
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;
const NSUInteger kDefaultPushNotificationsFullSyncInterval = 14400;
const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline = 600;

View File

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

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -13,12 +13,16 @@
/// limitations under the License.
#import "Source/common/SNTSystemInfo.h"
#include <sys/sysctl.h>
@implementation SNTSystemInfo
+ (NSString *)serialNumber {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
io_service_t platformExpert =
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
#pragma clang diagnostic pop
if (!platformExpert) return nil;
NSString *serial = CFBridgingRelease(IORegistryEntryCreateCFProperty(
@@ -30,8 +34,11 @@
}
+ (NSString *)hardwareUUID {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
io_service_t platformExpert =
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
#pragma clang diagnostic pop
if (!platformExpert) return nil;
NSString *uuid = CFBridgingRelease(IORegistryEntryCreateCFProperty(
@@ -60,6 +67,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

@@ -20,7 +20,7 @@
@protocol SNTDaemonControlXPC <SNTUnprivilegedDaemonControlXPC>
///
/// Kernel ops
/// Cache ops
///
- (void)flushCache:(void (^)(BOOL))reply;
@@ -41,7 +41,6 @@
/// Config ops
///
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)(void))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)(void))reply;
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
@@ -51,11 +50,12 @@
- (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;
- (void)setDisableUnknownEventUpload:(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,16 @@ 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;
#ifdef SANTAADHOC
// The mach service for an adhoc signed ES sysx uses the "endpoint-security" prefix instead of
// the teamid. In Santa's case it will be endpoint-security.com.google.santa.daemon.xpc.
return [NSString stringWithFormat:@"endpoint-security.%@.xpc", kBundleID];
#else
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
// "teamid.com.google.santa.daemon.xpc"
NSString *t = cs.signingInformation[@"teamid"];
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
#endif
}
+ (NSString *)systemExtensionID {

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,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2015-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.
@@ -15,8 +15,8 @@
#import <Foundation/Foundation.h>
#import <MOLCertificate/MOLCertificate.h>
#import "Source/common/SNTCommon.h"
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
@class SNTRule;
@class SNTStoredEvent;
@@ -28,12 +28,10 @@
@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
@@ -41,6 +39,7 @@
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive, int64_t teamID))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)staticRuleCount:(void (^)(int64_t count))reply;
///
/// Decision ops
@@ -64,7 +63,6 @@
/// Config ops
///
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;

View File

@@ -1,4 +1,4 @@
/// Copyright 2016 Google Inc. All rights reserved.
/// Copyright 2016-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.
@@ -17,31 +17,18 @@
#include <libkern/OSAtomic.h>
#include <libkern/OSTypes.h>
#include <os/log.h>
#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>
#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)
#include "Source/common/SNTCommon.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif // KERNEL
/**
A type to specialize to help SantaCache with its hashing.
@@ -88,8 +75,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 +84,7 @@ class SantaCache {
*/
~SantaCache() {
clear();
IOFreeAligned(buckets_, bucket_count_ * sizeof(struct bucket));
free(buckets_);
}
/**
@@ -173,7 +159,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 +270,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 +304,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 +320,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,8 +328,11 @@ 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))) {
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
0)) {
os_log_error(OS_LOG_DEFAULT,
"SantaCache::unlock(): Tried to unlock an unlocked lock");
abort();
}
}
@@ -375,8 +363,6 @@ class SantaCache {
}
};
#ifndef KERNEL
#pragma clang diagnostic pop
#endif
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H

60
Source/common/TestUtils.h Normal file
View File

@@ -0,0 +1,60 @@
/// 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.
#ifndef SANTA__COMMON__TESTUTILS_H
#define SANTA__COMMON__TESTUTILS_H
#include <EndpointSecurity/EndpointSecurity.h>
#import <XCTest/XCTest.h>
#include <bsm/libbsm.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <sys/stat.h>
#define NOBODY_UID ((unsigned int)-2)
#define NOBODY_GID ((unsigned int)-2)
// Bubble up googletest expectation failures to XCTest failures
#define XCTBubbleMockVerifyAndClearExpectations(mock) \
XCTAssertTrue(::testing::Mock::VerifyAndClearExpectations(mock), \
"Expected calls were not properly mocked")
// Pretty print C string match errors
#define XCTAssertCStringEqual(got, want) \
XCTAssertTrue(strcmp((got), (want)) == 0, @"\nMismatched strings.\n\t got: %s\n\twant: %s", \
(got), (want))
// Pretty print C++ string match errors
#define XCTAssertCppStringEqual(got, want) XCTAssertCStringEqual((got).c_str(), (want).c_str())
// Helper to ensure at least `ms` milliseconds are slept, even if the sleep
// function returns early due to interrupts.
void SleepMS(long ms);
enum class ActionType {
Auth,
Notify,
};
// Helpers to construct various ES structs
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver);
struct stat MakeStat(ino_t ino, dev_t devno = 0);
es_string_token_t MakeESStringToken(const char *s);
es_file_t MakeESFile(const char *path, struct stat sb = {});
es_process_t MakeESProcess(es_file_t *file, audit_token_t tok = {}, audit_token_t parent_tok = {});
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc,
ActionType action_type = ActionType::Notify,
uint64_t future_deadline_ms = 100000);
#endif

107
Source/common/TestUtils.mm Normal file
View File

@@ -0,0 +1,107 @@
/// 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.
#include "Source/common/TestUtils.h"
#include <EndpointSecurity/ESTypes.h>
#include <dispatch/dispatch.h>
#include <mach/mach_time.h>
#include <time.h>
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver) {
return audit_token_t{
.val =
{
0,
NOBODY_UID,
NOBODY_GID,
NOBODY_UID,
NOBODY_GID,
(unsigned int)pid,
0,
(unsigned int)pidver,
},
};
}
struct stat MakeStat(ino_t ino, dev_t devno) {
return (struct stat){
.st_dev = devno,
.st_ino = ino,
};
}
es_string_token_t MakeESStringToken(const char *s) {
return es_string_token_t{
.length = strlen(s),
.data = s,
};
}
es_file_t MakeESFile(const char *path, struct stat sb) {
return es_file_t{
.path = MakeESStringToken(path),
.path_truncated = false,
.stat = sb,
};
}
es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t parent_tok) {
return es_process_t{
.audit_token = tok,
.ppid = audit_token_to_pid(parent_tok),
.original_ppid = audit_token_to_pid(parent_tok),
.executable = file,
.parent_audit_token = parent_tok,
};
}
static uint64_t AddMillisToMachTime(uint64_t ms, uint64_t machTime) {
static dispatch_once_t onceToken;
static mach_timebase_info_data_t timebase;
dispatch_once(&onceToken, ^{
mach_timebase_info(&timebase);
});
// Convert given machTime to nanoseconds
uint64_t nanoTime = machTime * timebase.numer / timebase.denom;
// Add the ms offset
nanoTime += (ms * NSEC_PER_MSEC);
// Convert back to machTime
return nanoTime * timebase.denom / timebase.numer;
}
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType action_type,
uint64_t future_deadline_ms) {
return es_message_t{
.deadline = AddMillisToMachTime(future_deadline_ms, mach_absolute_time()),
.process = proc,
.action_type =
(action_type == ActionType::Notify) ? ES_ACTION_TYPE_NOTIFY : ES_ACTION_TYPE_AUTH,
.event_type = et,
};
}
void SleepMS(long ms) {
struct timespec ts {
.tv_sec = ms / 1000, .tv_nsec = (long)((ms % 1000) * NSEC_PER_MSEC),
};
while (nanosleep(&ts, &ts) != 0) {
XCTAssertEqual(errno, EINTR);
}
}

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

@@ -0,0 +1,147 @@
//
// !!! WARNING !!!
// This proto is for demonstration purposes only and will be changing.
// Do not rely on this format.
//
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;
optional string team_id = 14;
}
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

@@ -1,7 +1,12 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
load("//:helper.bzl", "santa_unit_test")
licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
)
exports_files([
"Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-256.png",
])
@@ -15,6 +20,10 @@ objc_library(
"SNTAccessibleTextField.m",
"SNTAppDelegate.h",
"SNTAppDelegate.m",
"SNTBinaryMessageWindowController.h",
"SNTBinaryMessageWindowController.m",
"SNTDeviceMessageWindowController.h",
"SNTDeviceMessageWindowController.m",
"SNTMessageWindow.h",
"SNTMessageWindow.m",
"SNTMessageWindowController.h",
@@ -23,21 +32,31 @@ objc_library(
"SNTNotificationManager.m",
"main.m",
],
hdrs = [
"SNTNotificationManager.h",
],
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:SNTSyncConstants",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCNotifierInterface",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
@@ -49,6 +68,7 @@ macos_application(
"//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/**"]),
@@ -59,14 +79,41 @@ macos_application(
"--force",
"--options library,kill,runtime",
],
entitlements = "Santa.app.entitlements",
entitlements = select({
"//:adhoc_build": "Santa.app-adhoc.entitlements",
# Non-adhoc builds get thier entitlements from the provisioning profile.
"//conditions:default": None,
}),
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
"//:adhoc_build": None,
"//conditions:default": "//profiles:santa_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":SantaGUI_lib"],
)
santa_unit_test(
name = "SNTNotificationManagerTest",
srcs = [
"SNTNotificationManagerTest.m",
],
sdk_frameworks = [
"Cocoa",
],
deps = [
":SantaGUI_lib",
"//Source/common:SNTStoredEvent",
"@OCMock",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTNotificationManagerTest",
],
visibility = ["//:santa_package_group"],
)

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>
@@ -17,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"/>
@@ -47,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>
@@ -60,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

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTAboutWindowController.h"
#import "Source/gui/SNTAboutWindowController.h"
#import "Source/common/SNTConfigurator.h"

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTAccessibleTextField.h"
#import "Source/gui/SNTAccessibleTextField.h"
@implementation SNTAccessibleTextField

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTAppDelegate.h"
#import "Source/gui/SNTAppDelegate.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -20,8 +20,8 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santa/SNTAboutWindowController.h"
#import "Source/santa/SNTNotificationManager.h"
#import "Source/gui/SNTAboutWindowController.h"
#import "Source/gui/SNTNotificationManager.h"
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;

View File

@@ -14,22 +14,19 @@
#import <Cocoa/Cocoa.h>
@class SNTStoredEvent;
#import "Source/gui/SNTMessageWindowController.h"
@protocol SNTMessageWindowControllerDelegate
- (void)windowDidCloseSilenceHash:(NSString *)hash;
@end
@class SNTStoredEvent;
///
/// Controller for a single message window.
///
@interface SNTMessageWindowController : NSWindowController
@interface SNTBinaryMessageWindowController : SNTMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
- (IBAction)showWindow:(id)sender;
- (IBAction)closeWindow:(id)sender;
- (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.
@@ -65,9 +62,4 @@
///
@property NSProgress *progress;
///
/// The delegate to inform when the notification is dismissed
///
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
@end

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTMessageWindowController.h"
#import "Source/gui/SNTBinaryMessageWindowController.h"
#import <MOLCertificate/MOLCertificate.h>
#import <SecurityInterface/SFCertificatePanel.h>
@@ -20,9 +20,9 @@
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santa/SNTMessageWindow.h"
#import "Source/gui/SNTMessageWindow.h"
@interface SNTMessageWindowController ()
@interface SNTBinaryMessageWindowController ()
/// The custom message to display for this event
@property(copy) NSString *customMessage;
@@ -36,11 +36,9 @@
/// 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
@implementation SNTBinaryMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
self = [super initWithWindowNibName:@"MessageWindow"];
@@ -77,9 +75,6 @@
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
if (![[SNTConfigurator configurator] eventDetailURL]) {
[self.openEventButton removeFromSuperview];
} else {
@@ -106,23 +101,8 @@
}
}
- (IBAction)showWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeIn:sender];
}
- (IBAction)closeWindow:(id)sender {
[self.progress cancel];
[(SNTMessageWindow *)self.window fadeOut:sender];
}
- (void)windowWillClose:(NSNotification *)notification {
if (!self.delegate) return;
if (self.silenceFutureNotifications) {
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
} else {
[self.delegate windowDidCloseSilenceHash:nil];
}
- (NSString *)messageHash {
return self.event.fileSHA256;
}
- (IBAction)showCertInfo:(id)sender {
@@ -159,7 +139,9 @@
- (NSString *)publisherInfo {
MOLCertificate *leafCert = [self.event.signingChain firstObject];
if (leafCert.commonName && leafCert.orgName) {
if ([leafCert.commonName isEqualToString:@"Apple Mac OS Application Signing"]) {
return [NSString stringWithFormat:@"App Store (Team ID: %@)", self.event.teamID];
} else if (leafCert.commonName && leafCert.orgName) {
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
} else if (leafCert.commonName) {
return leafCert.commonName;
@@ -175,4 +157,22 @@
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

@@ -11,23 +11,28 @@
/// 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 <DiskArbitration/DiskArbitration.h>
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#include <EndpointSecurity/EndpointSecurity.h>
#import "Source/common/SNTDeviceEvent.h"
#import "Source/gui/SNTMessageWindowController.h"
/*
* Manages DiskArbitration and EndpointSecurity to monitor/block/remount USB
* storage devices.
*/
@interface SNTDeviceManager : NSObject
NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readwrite) BOOL subscribed;
@property(nonatomic, readwrite) BOOL blockUSBMount;
@property(nonatomic, readwrite, nullable) NSArray<NSString *> *remountArgs;
@class SNTStoredEvent;
- (instancetype _Nonnull)init;
- (void)listen;
- (BOOL)subscribed;
///
/// 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/gui/SNTDeviceMessageWindowController.h"
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/gui/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

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTMessageWindow.h"
#import "Source/gui/SNTMessageWindow.h"
@implementation SNTMessageWindow

View File

@@ -0,0 +1,21 @@
#import <Cocoa/Cocoa.h>
@protocol SNTMessageWindowControllerDelegate
- (void)windowDidCloseSilenceHash:(NSString *)hash;
@end
@interface SNTMessageWindowController : NSWindowController
- (IBAction)showWindow:(id)sender;
- (IBAction)closeWindow:(id)sender;
/// Generate a distinct key for a given displayed event. This key is used for silencing future
/// notifications.
- (NSString *)messageHash;
/// Linked to checkbox in UI to prevent future notifications for the given event.
@property BOOL silenceFutureNotifications;
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
@end

View File

@@ -0,0 +1,35 @@
#import "Source/gui/SNTMessageWindowController.h"
#import "Source/gui/SNTMessageWindow.h"
@implementation SNTMessageWindowController
- (IBAction)showWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeIn:sender];
}
- (IBAction)closeWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeOut:sender];
}
- (void)windowWillClose:(NSNotification *)notification {
if (!self.delegate) return;
if (self.silenceFutureNotifications) {
[self.delegate windowDidCloseSilenceHash:[self messageHash]];
} else {
[self.delegate windowDidCloseSilenceHash:nil];
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
}
- (NSString *)messageHash {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
@end

View File

@@ -15,7 +15,9 @@
#import <Cocoa/Cocoa.h>
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/santa/SNTMessageWindowController.h"
#import "Source/gui/SNTBinaryMessageWindowController.h"
#import "Source/gui/SNTDeviceMessageWindowController.h"
#import "Source/gui/SNTMessageWindowController.h"
///
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.

View File

@@ -0,0 +1,364 @@
/// 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/gui/SNTNotificationManager.h"
#import <MOLCertificate/MOLCertificate.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/SNTSyncConstants.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/gui/SNTMessageWindowController.h"
@interface SNTNotificationManager ()
/// The currently displayed notification
@property SNTMessageWindowController *currentWindowController;
/// The queue of pending notifications
@property(readonly) NSMutableArray *pendingNotifications;
// A serial queue for holding hashBundleBinaries requests
@property dispatch_queue_t hashBundleBinariesQueue;
@end
@implementation SNTNotificationManager
static NSString *const silencedNotificationsKey = @"SilencedNotifications";
- (instancetype)init {
self = [super init];
if (self) {
_pendingNotifications = [[NSMutableArray alloc] init];
_hashBundleBinariesQueue =
dispatch_queue_create("com.google.santagui.hashbundlebinaries", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)windowDidCloseSilenceHash:(NSString *)hash {
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
if (self.pendingNotifications.count) {
[self showQueuedWindow];
} else {
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
[bc resume];
[[bc remoteObjectProxy] spindown];
[bc invalidate];
[NSApp hide:self];
}
}
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
if (!d) d = [NSMutableDictionary dictionary];
if (date) {
d[hash] = date;
} else {
[d removeObjectForKey:hash];
}
[ud setObject:d forKey:silencedNotificationsKey];
}
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
for (SNTMessageWindowController *msg in self.pendingNotifications) {
if ([[msg messageHash] isEqual:[pendingMsg messageHash]]) return YES;
}
return NO;
}
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
NSString *messageHash = [pendingMsg messageHash];
if ([self notificationAlreadyQueued:pendingMsg]) return;
// See if this message is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
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:messageHash];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:messageHash];
} else {
LOGI(@"Notification silence: dropping notification for %@", messageHash);
return;
}
}
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
[self postDistributedNotification:pendingMsg];
if (!self.currentWindowController) {
[self showQueuedWindow];
}
}
// For blocked execution notifications, post an NSDistributedNotificationCenter
// notification with the important details from the stored event. Distributed
// notifications are system-wide broadcasts that can be sent by apps and observed
// from separate processes. This allows users of Santa to write tools that
// perform actions when we block execution, such as trigger management tools or
// display an enterprise-specific UI (which is particularly useful when combined
// with the EnableSilentMode configuration option, to disable Santa's standard UI).
- (void)postDistributedNotification:(SNTMessageWindowController *)pendingMsg {
if (![pendingMsg isKindOfClass:[SNTBinaryMessageWindowController class]]) {
return;
}
SNTBinaryMessageWindowController *wc = (SNTBinaryMessageWindowController *)pendingMsg;
NSDistributedNotificationCenter *dc = [NSDistributedNotificationCenter defaultCenter];
NSMutableArray<NSDictionary *> *signingChain =
[NSMutableArray arrayWithCapacity:wc.event.signingChain.count];
for (MOLCertificate *cert in wc.event.signingChain) {
[signingChain addObject:@{
kCertSHA256 : cert.SHA256 ?: @"",
kCertCN : cert.commonName ?: @"",
kCertOrg : cert.orgName ?: @"",
kCertOU : cert.orgUnit ?: @"",
kCertValidFrom : @([cert.validFrom timeIntervalSince1970]) ?: @0,
kCertValidUntil : @([cert.validUntil timeIntervalSince1970]) ?: @0,
}];
}
NSDictionary *userInfo = @{
kFileSHA256 : wc.event.fileSHA256 ?: @"",
kFilePath : wc.event.filePath ?: @"",
kFileBundleName : wc.event.fileBundleName ?: @"",
kFileBundleID : wc.event.fileBundleID ?: @"",
kFileBundleVersion : wc.event.fileBundleVersion ?: @"",
kFileBundleShortVersionString : wc.event.fileBundleVersionString ?: @"",
kTeamID : wc.event.teamID ?: @"",
kExecutingUser : wc.event.executingUser ?: @"",
kExecutionTime : @([wc.event.occurrenceDate timeIntervalSince1970]) ?: @0,
kPID : wc.event.pid ?: @0,
kPPID : wc.event.ppid ?: @0,
kParentName : wc.event.parentName ?: @"",
kSigningChain : signingChain,
};
[dc postNotificationName:@"com.google.santa.notification.blockedeexecution"
object:@"com.google.santa"
userInfo:userInfo];
}
- (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(), ^{
// 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 = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
SNTBinaryMessageWindowController *controller =
(SNTBinaryMessageWindowController *)self.currentWindowController;
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:controller.event withController:controller];
});
}
}
});
}
- (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];
bc.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
[bc resume];
// 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))) {
[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.
[withController.progress becomeCurrentWithPendingUnitCount:100];
// 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 [withController updateBlockNotification:event
withBundleHash:nil];
event.fileBundleHash = bh;
event.fileBundleBinaryCount = @(events.count);
event.fileBundleHashMilliseconds = ms;
event.fileBundleExecutableRelPath =
[events.firstObject fileBundleExecutableRelPath];
for (SNTStoredEvent *se in events) {
se.fileBundleHash = bh;
se.fileBundleBinaryCount = @(events.count);
se.fileBundleHashMilliseconds = ms;
}
// Send the results to santad. It will decide if they need to be
// synced.
MOLXPCConnection *daemonConn =
[SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] syncBundleEvent:event
relatedEvents:events];
[daemonConn invalidate];
// Update the UI with the bundle hash. Also make the openEventButton
// available.
[withController updateBlockNotification:event withBundleHash:bh];
[bc invalidate];
}];
[withController.progress resignCurrent];
}
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
if ([SNTConfigurator configurator].enableSilentMode) return;
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 {
if ([SNTConfigurator configurator].enableSilentMode) return;
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 ([SNTConfigurator configurator].enableSilentMode) return;
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 ([SNTConfigurator configurator].enableSilentMode) return;
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

@@ -0,0 +1,76 @@
/// 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 <MOLCertificate/MOLCertificate.h>
// #import <MOLCodesignChecker/MOLCodesignChecker.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Source/gui/SNTNotificationManager.h"
#import "Source/common/SNTStoredEvent.h"
@class SNTBinaryMessageWindowController;
@interface SNTNotificationManager (Testing)
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
withController:(SNTBinaryMessageWindowController *)controller;
@end
@interface SNTNotificationManagerTest : XCTestCase
@end
@implementation SNTNotificationManagerTest
- (void)setUp {
[super setUp];
fclose(stdout);
}
- (void)testPostBlockNotificationSendsDistributedNotification {
SNTStoredEvent *ev = [[SNTStoredEvent alloc] init];
ev.fileSHA256 = @"the-sha256";
ev.filePath = @"/Applications/Safari.app/Contents/MacOS/Safari";
ev.fileBundleName = @"Safari";
ev.fileBundlePath = @"/Applications/Safari.app";
ev.fileBundleID = @"com.apple.Safari";
ev.fileBundleVersion = @"18614.1.14.1.15";
ev.fileBundleVersionString = @"16.0";
ev.executingUser = @"rah";
ev.occurrenceDate = [NSDate dateWithTimeIntervalSince1970:1660221048];
ev.decision = SNTEventStateBlockBinary;
ev.pid = @84156;
ev.ppid = @1;
ev.parentName = @"launchd";
SNTNotificationManager *sut = OCMPartialMock([[SNTNotificationManager alloc] init]);
OCMStub([sut hashBundleBinariesForEvent:OCMOCK_ANY withController:OCMOCK_ANY]).andDo(nil);
id dncMock = OCMClassMock([NSDistributedNotificationCenter class]);
OCMStub([dncMock defaultCenter]).andReturn(dncMock);
[sut postBlockNotification:ev withCustomMessage:@""];
OCMVerify([dncMock postNotificationName:@"com.google.santa.notification.blockedeexecution"
object:@"com.google.santa"
userInfo:[OCMArg checkWithBlock:^BOOL(NSDictionary *userInfo) {
XCTAssertEqualObjects(userInfo[@"file_sha256"], @"the-sha256");
XCTAssertEqualObjects(userInfo[@"pid"], @84156);
XCTAssertEqualObjects(userInfo[@"ppid"], @1);
XCTAssertEqualObjects(userInfo[@"execution_time"], @1660221048);
return YES;
}]]);
}
@end

View File

@@ -0,0 +1,8 @@
<?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>com.apple.developer.system-extension.install</key>
<true/>
</dict>
</plist>

View File

@@ -17,7 +17,7 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santa/SNTAppDelegate.h"
#import "Source/gui/SNTAppDelegate.h"
@interface SNTSystemExtensionDelegate : NSObject <OSSystemExtensionRequestDelegate>
@end
@@ -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,266 +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 "Source/santa/SNTNotificationManager.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTBlockMessage.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"
@interface SNTNotificationManager ()
/// The currently displayed notification
@property SNTMessageWindowController *currentWindowController;
/// The queue of pending notifications
@property(readonly) NSMutableArray *pendingNotifications;
// A serial queue for holding hashBundleBinaries requests
@property dispatch_queue_t hashBundleBinariesQueue;
@end
@implementation SNTNotificationManager
static NSString *const silencedNotificationsKey = @"SilencedNotifications";
- (instancetype)init {
self = [super init];
if (self) {
_pendingNotifications = [[NSMutableArray alloc] init];
_hashBundleBinariesQueue =
dispatch_queue_create("com.google.santagui.hashbundlebinaries", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)windowDidCloseSilenceHash:(NSString *)hash {
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
[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];
});
}
} else {
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
[bc resume];
[[bc remoteObjectProxy] spindown];
[bc invalidate];
[NSApp hide:self];
}
}
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
if (!d) d = [NSMutableDictionary dictionary];
if (date) {
d[hash] = date;
} else {
[d removeObjectForKey:hash];
}
[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;
}
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
// See if this binary is already in the list of pending notifications.
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
// See if this binary is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
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];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else {
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
return;
}
}
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
// 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.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
}
});
}
- (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...";
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
bc.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
[bc resume];
// 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];
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];
// 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];
event.fileBundleHash = bh;
event.fileBundleBinaryCount = @(events.count);
event.fileBundleHashMilliseconds = ms;
event.fileBundleExecutableRelPath =
[events.firstObject fileBundleExecutableRelPath];
for (SNTStoredEvent *se in events) {
se.fileBundleHash = bh;
se.fileBundleBinaryCount = @(events.count);
se.fileBundleHashMilliseconds = ms;
}
// Send the results to santad. It will decide if they need to be
// synced.
MOLXPCConnection *daemonConn =
[SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] syncBundleEvent:event
relatedEvents:events];
[daemonConn invalidate];
// Update the UI with the bundle hash. Also make the openEventButton
// available.
[self updateBlockNotification:event withBundleHash:bh];
[bc invalidate];
}];
[self.currentWindowController.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];
}
});
}
@end

View File

@@ -1,16 +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>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

@@ -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:SNTKernelCommon",
"//Source/common:SNTLoggingKernel",
"//Source/common:SNTPrefixTreeKernel",
"//Source/common:SantaCache",
],
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

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