Compare commits

..

188 Commits
1.3 ... 2021.8

Author SHA1 Message Date
Pete Markowsky
82b71c0f20 Add a metrics command to santactl (#687)
Add a metrics command to santactl.
2021-12-02 14:30:39 -05:00
Allister Banks
10ccee9e4c Docs: EnableSysxCache docs, etc (#684)
* Add more Conf references, EnableSysxCache key, etc
* Updated link (even though previous config profile explainer link redirects accordingly) to profile spec.
* Added brief explanation of TCC/PPPC and made reference to the non-setting example MDM mobileconfig files in the repo
* Add sysext log stream example, update wording
* Pointed at events and configuration pages for details about logging
* New troubleshooting section
* Standardized on asterisks for page link markup in the TOC index page
2021-11-22 22:22:49 -05:00
Pete Markowsky
acbbb9e7b0 Add a configuration option for users to add their own root labels (#683)
Add an option for users to add their own root labels.

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

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

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

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

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

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

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

* Create a local keychain instead of using the system keychain and drop sudo from most of the build stages
2021-09-28 12:48:09 -04:00
Kent Ma
81049db170 Deflake SNTApplicationTest by tracking subscriptions to specific event types (#614)
* Switch to waitForExpectations in tests

* Change mock es_subscribe to note specific events we're ready for
2021-09-27 10:40:15 -04:00
Russell Hancox
c110245701 Project: fix exporting of SantaCache header (#612) 2021-09-23 12:32:41 -04:00
Russell Hancox
d7a56b9bd4 Project: fix some BUILD file lint (#611) 2021-09-23 12:03:23 -04:00
Russell Hancox
4bb5804a6f santactl/sync: Catch rare crash in FCM parsing (#609) 2021-09-23 10:56:57 -04:00
Russell Hancox
e68fb7235a Metric: Fix formatting of SNTMetricFormatTestHelper (#608) 2021-09-23 06:53:06 -04:00
Pete Markowsky
f93e7ef879 Refactored metric service tests to use a common helper. (#607)
This refactors the SNTFormat tests to use the SNTMetricSet to generate the
test data. This keeps the metric service and the SNTMetricSet in sync and
reduces repeated data.
2021-09-22 16:56:57 -04:00
Kent Ma
f472f4821c Create block builder for ES Messages and clean up tests (#606)
Create block builder for ES Messages and clean up tests
2021-09-22 15:48:24 -04:00
Pete Markowsky
1c97761038 Initial commit of santametricservice. (#605)
Initial commit of santametricservice.

The santametricservice is an XPC helper service to write metrics. It consists of Formatters and Writers. This initial commit only has support for the rawJSON format and writing to a file.

This is a new daemon to be included. Docs and packaging will be updated in future PRs.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-09-22 14:49:30 -04:00
Pete Markowsky
e569a684b7 Add initial configuration options for barebones metric service (#604)
Add initial configuration options for barebones metric service.
2021-09-21 13:36:02 -04:00
Pete Markowsky
66c32dc526 Added an XPC interface for the Metric service. (#603) 2021-09-20 18:14:39 -04:00
Kent Ma
075d3cbc11 Include an integration test setup and fixture with moroz (#602) 2021-09-20 11:35:12 -04:00
Adam Sindelar
340326df8a Remove the hiring banner (#600) 2021-09-10 08:09:06 -04:00
Kent Ma
f52edd2a76 Explicitly include the TeamIdentifierPrefix to santad's entitlement (#599) 2021-09-09 11:24:49 -04:00
Kent Ma
11c247e33a Add entitlements to the santad and Santa build rules (#598) 2021-09-09 09:36:42 -04:00
Ryan Diers
a859b9b341 Docs: Add Rudolph to sync server list (#597) 2021-09-08 19:41:27 -04:00
Kent Ma
c190f1f52d Add tulsi project (#596) 2021-09-08 14:50:57 -04:00
Pete Markowsky
87dc191494 Rewrote ci.yml workflow to parallelize steps. (#593)
Build and test steps were taking more than 5 minutes to run so this cleans up
the workflow and parallelizes it.
2021-09-02 17:21:36 -04:00
Pete Markowsky
3a19591822 Made CI build and test steps conditional. (#592)
This changes the workflow steps to execute conditionally e.g. if you only modify
documentation it won't run the build, test, and coverage steps.
2021-09-01 16:47:19 -04:00
Kent Ma
b225c0740e Raise the timeout delay for tests (#591) 2021-08-25 16:46:26 -04:00
Russell Hancox
d1fffb4636 README: Remove ReadTheDocs link (#589) 2021-08-25 12:50:07 -04:00
Kent Ma
9d7ca62e46 Pin to 0a2c39c020 (#588)
This allows us to run tests on ARM.
2021-08-19 12:01:45 -04:00
Kent Ma
2a6073a9a1 Upgrade bazel rules_apple to 0.31.3 (#587) 2021-08-18 17:04:49 -04:00
Russell Hancox
296f06582b Project: delete obsolete ReadTheDocs config (#586)
We haven't used ReadTheDocs for >6m
2021-08-18 15:40:34 -04:00
Russell Hancox
0e27dab4c6 Project: remove xcode projects, they're stale (#584) 2021-08-18 15:05:55 -04:00
Russell Hancox
256836d7f8 Docs: Switch themes, update config, fix redirect (#583) 2021-08-18 10:58:12 -04:00
Russell Hancox
b117d8106e Project: Update deps in WORKSPACE to be reproducible. (#582)
This removes a lot of warnings when starting in a clean workspace
2021-08-17 17:20:30 -04:00
Russell Hancox
c980223215 Project: remove py2 forcing, drop deprecation warnings back to warning (#581) 2021-08-17 16:59:27 -04:00
Russell Hancox
635b33ebf9 Project: Update coverage link in README (#580) 2021-08-17 15:53:07 -04:00
Kent Ma
b6f35c9b9f Add actual test binaries to the repo (#579) 2021-08-17 15:02:14 -04:00
Kent Ma
796109cc60 Adding cert rules and testdata (#578) 2021-08-17 14:26:09 -04:00
Russell Hancox
38f580de72 CI: Re-org CI workflows, ignore /Applications in coverage (#577) 2021-08-17 14:08:02 -04:00
Kent Ma
c7a58c77e7 Add missing ES Auth response to AUTH_RENAME (#576)
* Add missing ES Auth response to AUTH_RENAME
* Added unit test cases for benign paths
2021-08-17 12:10:43 -04:00
Kent Ma
9a4fe782d7 Bump version -> 2021.7 (#575) 2021-08-16 16:04:24 -04:00
Kent Ma
fbb5f3728f Include license in BUILD file (#574) 2021-08-16 12:55:01 -04:00
Pete Markowsky
24b96c4798 Added types for recording and storing performance metrics. (#567)
* Added types for recording and storing performance metrics.

This adds SNTMetricSet and various gauge and counter types to allow for
exporting metrics such as CPU,Memory usage and other properties that are useful
for tracking reliability and debugging.

This is the first commit of a series to add support for monitoring systems.

Co-authored-by: Kent Ma <tnek@google.com>
2021-08-13 13:26:45 -04:00
Kent Ma
1edf6d9200 Enable -Werror -Wall on our build rules (#572)
* Reorder init lists for -Wreorder-init-lists

* Add nullability annotations to the rest of EndpointSecurityTestUtil

* Added fake uses for -Wunused-variable

* Corrected signed/unsigned int conversions in SNTPrefixTree

* Explicitly convert implicit conversions in Santacache

* Set bazelrc to -Werror -Wall
2021-08-13 11:31:41 -04:00
Kent Ma
ac1f8ea1b8 Add an extra return on the rename case (#570) 2021-08-12 13:48:38 -04:00
Kent Ma
9923f601b6 Prevent Overwrite of Santa Databases (#569) 2021-08-12 10:38:27 -04:00
Kent Ma
471ae89406 Switch the CI build steps to run all unit tests and coverage first (#568) 2021-08-11 14:03:05 -04:00
Kent Ma
54d6653973 Include SNTEndpointSecurityManagerTest in the main test_suite (#566)
* Include SNTEndpointSecurityManagerTest in the main test_suite and clean it up.

This commit (1) adds es_unsubscribe and es_delete_client to our ESF shim
to fix the test segfaulting, and (2) cleans up the unit tests themselves by
breaking out the timeout test from the regular unlink test
2021-08-11 11:42:55 -04:00
Kent Ma
27ee66597b Correctly calculate coverage by calling blaze coverage on individual tests (#565) 2021-08-11 09:17:27 -04:00
Kent Ma
10f2d852f5 Add functional test for executing a binary on SNTApplication. (#562)
This adds a full functional test for starting up an SNTApplication
(with as few mocks as possible) and executing it with a directly
recorded & collected EndpointSecurity event.

This also fixes a potential race condition and segfault on Santa startup: due
to es_subscribe being called first, it's possible for an es event to arrive
before listenForDecisionRequests or listenForLogRequests are called,
causing the SNTEndpointSecurityManager callbacks to call a nil pointer.
2021-08-10 14:42:21 -04:00
Kent Ma
1fcb63dc92 Add coveralls CI rule and lcov generator (#564)
Adding coveralls so that test coverage can be easily displayed and checked
2021-08-10 14:41:14 -04:00
Kent Ma
7944f681f8 Test util library for mocking EndpointSecurity. (#560)
* Test util library for mocking EndpointSecurity.
2021-07-14 10:49:10 -04:00
Tom Burgin
e3aedc92ba bump version (#559) 2021-06-16 13:20:10 -04:00
Kent Ma
d2b6c2b6c2 Update .clang-format and apply to existing files. (#558) 2021-06-15 09:47:31 -04:00
Tom Burgin
d026989dfb santactl: remove extra check (#557) 2021-06-11 15:43:08 -04:00
Tom Burgin
e7a8e9b6ac santactl: use host instead of http address for reachability (#556) 2021-06-11 15:13:50 -04:00
Russell Hancox
1d9af01353 Project: Bump dependency versions, fix reload command (#554)
* Project: Bump dependency versions, fix reload command

The reload command would fail if you used multiple compilation modes for
building as it would try extracting the versions from both comp modes.

The dependency bump includes a fix for #553
2021-05-03 21:37:42 -04:00
Adam Sindelar
9c6af7fc03 Docs: Add job posting link to README 2021-04-26 12:16:35 -04:00
Tom Burgin
543b1a29fe add default provisioning profile rules (#548) 2021-04-19 17:18:18 -04:00
Tom Burgin
625ec67789 handle PHONE_REGISTRATION_ERROR (#549) 2021-04-19 17:16:53 -04:00
Tom Burgin
c5696d71e7 add build release rule (#547) 2021-04-19 13:58:59 -04:00
Tom Burgin
5f3cef52de cleanup (#546) 2021-04-19 13:37:21 -04:00
Tom Burgin
eeed0b5aa6 santactl: migrate from fcmstream to fcmconnection (#545) 2021-04-19 11:51:32 -04:00
Russell Hancox
9ef171e663 Docs: Fix more broken docs links (#543) 2021-04-19 11:17:13 -04:00
Russell Hancox
ad1868a50f santad: Fix transitive rules when using the sysx cache feature (#540)
This fixes transitive allowlisting when `EnableSysxCache` is turned on, reduces the deadline timer to fire 5s before the ES deadline, remaps our DEBUG logs to NOTICE so they can be more easily seen in Console and prevents transitive rules being created for paths under /dev/.
2021-03-04 09:47:32 -05:00
Russell Hancox
78643d3c49 fileinfo: Don't use non-bundle dirs as possible ancestors (#537) 2021-02-01 11:09:32 -05:00
Russell Hancox
8b22c85a64 Project: run buildifier on BUILD files (#534) 2021-01-28 10:31:07 -05:00
Russell Hancox
58fe5d3d76 santad: Use OS_FALLTHROUGH (#535) 2021-01-28 10:30:47 -05:00
Russell Hancox
8b2227967e santad: Fix caching of deny decisions (#533) 2021-01-28 10:12:20 -05:00
Russell Hancox
65693acea1 Docs: fix syncing-overview link in santactl doc (#531) 2021-01-27 12:35:02 -05:00
Russell Hancox
7cea383930 Docs: the docs build can't use symlinks ref. out of the docs dir (#530) 2021-01-27 12:25:50 -05:00
headmin
5ae2376158 Docs: Add example .mobileconfig profile to enable Notifications settings (#529) 2021-01-27 11:00:34 -05:00
Russell Hancox
e851337eac Docs: Fix some broken links in the index (#528) 2021-01-27 10:32:30 -05:00
Russell Hancox
2e53834980 santactl/sync: retry individual requests during a sync (#526)
Each request is retried up to 5 times with gaps of 2s, 4s, 6s, 8s
2021-01-26 15:58:52 -05:00
Hugh Neale
aef139e93c The configuration key "enabled_transitive_rules" should be "enable_transitive_rules" (#525) 2021-01-26 14:20:15 -05:00
Russell Hancox
a9e5bf09a7 santad: Add some TODOs related to cache 2021-01-11 13:16:38 -05:00
Russell Hancox
4ee3f281c3 santactl/status: Output cache details for sysx 2021-01-11 13:16:38 -05:00
Russell Hancox
462ce89d42 Project: Fix test locations 2021-01-11 13:16:38 -05:00
Russell Hancox
44117833c0 Project: Fix build rule 2021-01-11 13:16:38 -05:00
Russell Hancox
8b6e029da2 Project: bump version to 2021.1
This is a new versioning scheme.
2021-01-11 13:16:38 -05:00
Russell Hancox
f183e246df santad: Make use of caching endpoint security optional 2021-01-11 13:16:38 -05:00
Russell Hancox
c60a35f280 santad: Add caching layer to EndpointSecurity
This first commit is very rough, just adding the caching as simply as
possible. Refactoring is needed.
2021-01-11 13:16:38 -05:00
Russell Hancox
4f65965277 santactl/fileinfo: Fix fileinfo tests on BigSur + multiarch plists (#523)
The fileinfo tests didn't work on BigSur because of some path and binary changes.

Also, the embeddedPlist method didn't work on fat binaries, of which there are now
many, because of M1 machines. I think we didn't notice this before because we pull
the embedded plist from the first arch listed in the headers dict which generally
seemed to pick x86_64 first but with the arm64/arm64e option being added
that now appears first.

Also fixed some errors handling 32-bit segment/sections and added a test for this.
2021-01-07 19:46:48 -05:00
Tom Burgin
01e4e15b81 santactl sync: add config option to enable legacy zlib content encoding (#522) 2020-12-23 10:36:39 -05:00
Russell Hancox
532cb37e0b CI: split out driver and userspace builds (#521) 2020-12-23 08:38:39 -05:00
Tom Burgin
9d379d3884 release: split out the kext into a separate release label (#520)
* fix SNTLoggingKernel BUILD rule (#518)

* release: split out santa-driver.kext

* release: update ci

* remove ipa script rule

* update ci
2020-12-19 18:23:54 -05:00
Tom Burgin
3e7a191bf7 fix SNTLoggingKernel BUILD rule (#518) 2020-12-17 16:35:13 -05:00
Ryan Diers
c5a048f4d9 santactl/sync: Use deflate as Content-Encoding instead of zlib
The latter was not standards-compliant.
2020-12-14 16:19:48 -05:00
Hugh Neale
f4769bad90 Added Zercurity to list of available sync servers (#511) 2020-12-08 20:29:28 -05:00
Russell Hancox
254497ad15 Project: don't reference obsolete rake commands in CONTRIBUTING (#513) 2020-12-08 09:57:42 -05:00
avanzini
0a83445838 Log pidversion along with pid. (#512) 2020-12-08 09:46:34 -05:00
Tom Burgin
eff287259e project: update Xcode project to build universal binaries (#509) 2020-11-17 16:18:16 -05:00
Russell Hancox
6f2c0e3457 Project: remove Travis, update CI status in README (#508) 2020-11-02 09:59:35 -05:00
Russell Hancox
38769f7cd1 Project: Add GitHub Actions CI workflow (#507) 2020-10-30 12:23:01 -04:00
Russell Hancox
fa785ad3c2 Kernel: fix some header imports (#505) 2020-10-26 10:05:25 -04:00
Russell Hancox
5dae0cabdd Project: fix some lint (#504) 2020-10-22 14:01:32 -04:00
Russell Hancox
a8b4f4ea7e Project: move travis to xcode12 (#503) 2020-10-22 13:50:32 -04:00
Russell Hancox
2221c93bbc santa-driver: Fix some new Xcode 12 warnings (#502)
The ossharedptr-misuse warning is generated from within system headers and I couldn't
find a simple way to prevent that other than disabling the warning entirely. We don't
use OSSharedPtr directly anyway.
2020-10-22 13:41:31 -04:00
Tom Burgin
d1c33baf35 project: add EnableDebugLogging option (#501)
* project: add EnableDebugLogging option

* review updates
2020-10-22 10:11:18 -04:00
Tom Burgin
d2bbdff373 Add the option to ignore actions from other ES clients (#498)
* [com.google.santa.daemon]: add the option to ignore actions from other ES clients

* review updates

* review updates
2020-10-21 13:20:13 -04:00
Russell Hancox
db1d65f944 Project: Update dependency versions (#500)
MOLAuthenticatingURLSession: v2.8 -> v2.9
rules_apple: v0.19.0 -> v0.20.0
2020-10-21 11:55:38 -04:00
Hugh Neale
d17aeac2f4 Make it possible to remotely set the FullSyncInterval (#494)
Make it possible for the sync server to set the FullSyncInterval with "full_sync_interval" during `preflight`
2020-10-01 13:47:55 -04:00
Hugh Neale
7840270dd0 Support for %hostname%, %uuid% and %serial% to eventDetailURLForEvent (#493)
Added support for %hostname%, %uuid% and %serial% to eventDetailURLForEvent to provide additional system information for blocked events & updated documentation references for supported URL params.
2020-08-31 10:38:35 -04:00
Russell Hancox
dcf44c9872 Fix video in README (#492)
Fixes #491
2020-08-27 17:28:40 -04:00
Russell Hancox
fc365c888f Create CNAME 2020-08-27 16:21:08 -04:00
Russell Hancox
85f0782399 Delete CNAME 2020-08-27 16:21:03 -04:00
Russell Hancox
64bc34c302 santactl/rule: make flags consistent with help text (#486) 2020-07-29 13:39:41 -04:00
Russell Hancox
e2fc4c735d santad: Prevent kext from being loaded when ES is running (#484) 2020-07-21 10:18:22 -04:00
Russell Hancox
ff9cb34490 Project: avoid public visibility (#483) 2020-07-20 12:19:14 -04:00
Russell Hancox
60405f1e10 Fix some recent warnings (#482) 2020-07-20 11:36:25 -04:00
Edward Eigerman
ac9d3b2adf Update AboutWindow.xib (#481)
Remove the word "whitelist" from the user-facing window.
2020-07-17 22:11:23 -04:00
Russell Hancox
7e8bd46da3 Docs: fix readthedocs config (#480)
Fixes #479
2020-07-16 12:37:45 -04:00
Tom Burgin
2f6ed455e5 add fork and exit logging (#478)
* added fork and exit logging

* what did you use?

* review updates
2020-07-09 16:36:23 -04:00
Tom Burgin
8cb86b6d1d syncservice: create stub for syncservice (#477)
* stub for santasyncservice

* update protocol
2020-07-08 15:42:42 -04:00
Russell Hancox
fc074f6014 santactl: Make logging around rule download clearer (#476) 2020-07-08 10:09:56 -04:00
bfreezy
a7856e60e8 Add example System Extension and TCC configuration profiles (#474)
* add system extension policy example

* add tcc profile policy example

* set bundle ID to com.google.santa.daemon
2020-06-11 20:44:59 -04:00
Russell Hancox
41a40c9fbd Docs: remove whitelist/blacklist (#471) 2020-06-08 13:46:18 -04:00
Russell Hancox
8c18f6ebf5 Project: Update terminology in README (#470) 2020-06-08 12:41:44 -04:00
Tom Burgin
949053fedd update kext cache (#469) 2020-06-08 11:15:22 -04:00
Russell Hancox
8d2c39b71d Project: update whitelist/blacklist -> allowlist/blocklist (part 1: code) (#468) 2020-06-08 11:11:30 -04:00
Russell Hancox
8f872fb4fc Project: disable known deprecated warnings (#467) 2020-06-04 11:52:24 -04:00
Russell Hancox
5512f8cf19 santad/sysx: Prevent unlinking databases (#465)
* santad/sysx: Prevent unlinking databases
2020-06-01 13:21:30 -04:00
Russell Hancox
6742b38e31 santad: If database is locked don't attempt to unlink it (#466)
* santad: If database is locked don't attempt to unlink it
2020-05-29 17:22:23 -04:00
Russell Hancox
d1635f7e11 santad: Fix decision fetching for certs by hash (#464)
* santad: Fix decision fetching for certs by hash

Fixes #463
2020-05-11 11:43:07 -04:00
Tom Burgin
e2b865c081 prevent a dual duel (#462)
* prevent a dual duel

* bump version
2020-05-04 11:42:08 -04:00
Bradley Kemp
012b02de5d Update EventDetailURL docs
%bundle_id% and %bundle_ver% do not exist any more, they were removed by 6f417a1775 (diff-3250262f27ab2cb96ad4b47abdc9d51fL95-L108)
2020-05-01 07:22:57 -04:00
Russell Hancox
11ebead617 Add security policy link to README 2020-04-08 13:26:05 -04:00
Russell Hancox
e3fbabfe37 Create SECURITY.md 2020-04-08 13:26:05 -04:00
Russell Hancox
8757da7822 Version bump to 1.13 2020-04-07 17:14:02 -04:00
Russell Hancox
428582f471 santa-driver: fix use-after-free race in Get*MemoryDescriptor() 2020-04-07 17:14:02 -04:00
Russell Hancox
6e0effc0f4 santa-driver: fix off-by-one bug in externalMethod 2020-04-07 17:14:02 -04:00
Russell Hancox
683114fbec santa-driver: fix integer overflow/underflow in bucket_counts() 2020-04-07 17:14:02 -04:00
Tom Burgin
d9ebb4e3db version bump (#455) 2020-03-17 16:27:40 -04:00
Tom Burgin
e6aaf2f198 Santa.app: don't request SystemExtension loading (#454) 2020-03-17 16:23:48 -04:00
Tom Burgin
1c3757d4ab santactl: don't watch for config changes (#453)
* santactl: don't watch for config changes

* bump version
2020-03-16 18:40:36 -04:00
Tom Burgin
4346bb29c2 santactl: sanitize rule payload (#450)
* santactl: sanitize rule payload

* version bump
2020-02-27 15:16:40 -05:00
Tom Burgin
09655df8fc com.google.santa.daemon: reorder cleanup() (#448)
* com.google.santa.daemon: reorder cleanup()

* version bump
2020-02-26 15:13:51 -05:00
Tom Burgin
7504cd36e1 Simplify install scripts (#447)
* installer to respect EnableSystemExtension

* conform
2020-02-26 12:58:12 -05:00
Tom Burgin
cafef66933 version bump (#446) 2020-02-25 15:14:42 -05:00
Tom Burgin
0c4e9d4b06 slurp up com.google.santa.daemon dsyms (#445) 2020-02-21 18:28:15 -05:00
Tom Burgin
ac07f5d54b santad: add prefixes on a background thread (#444)
* add prefixes on a background thread

* version bump
2020-02-21 16:54:42 -05:00
Tom Burgin
d116f7b01e santad: wait for driver connection before adding prefix filters (#443)
* wait for driver connection before adding prefix filters

* version bump

* fix travis build
2020-02-21 14:58:12 -05:00
Tom Burgin
63ca34bc54 santad: fix launch path and args for loading the system extension (#442)
* missing /

* version bump

* that was close
2020-02-20 20:01:42 -05:00
263 changed files with 12067 additions and 4596 deletions

View File

@@ -1,2 +1,5 @@
build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
build --host_force_python=PY2
build --copt=-Werror
build --copt=-Wall
build --copt=-Wno-error=deprecated-declarations

View File

@@ -1,18 +1,18 @@
Language: ObjC
BasedOnStyle: Google
Language: Cpp
Standard: Cpp11
# Disable ColumnLimit because it causes some very weird line breaks.
# For ObjC the limit is 100
# For Cpp the limit is 80
ColumnLimit: 0
IndentWidth: 2
ObjCBlockIndentWidth: 2
ContinuationIndentWidth: 2
# For ObjC, the line limit is 100
ColumnLimit: 100
# Allow short case statements to be on a single line
AllowShortCaseLabelsOnASingleLine: true
# Ban short loops and functions on a single line
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
# Allow spaces in NSArray/NSDictionary literals @[ and @{
SpacesInContainerLiterals: true
@@ -20,3 +20,13 @@ SpacesInContainerLiterals: true
# For pointers, always put the * next to the variable name.
DerivePointerAlignment: false
PointerAlignment: Right
---
Language: Cpp
Standard: Cpp11
BasedOnStyle: Google
# For C++, the line limit is 80
ColumnLimit: 80

View File

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

126
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,126 @@
name: CI
on:
push:
branches:
- '*'
pull_request:
branches:
- main
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
run: ./Testing/lint.sh
build_userspace:
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
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
unit_tests:
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
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
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
run: sh ./generate_cov.sh
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./CoverageData/info.lcov
flag-name: Unit
benchmark:
runs-on: macos-11
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Run All Tests
run: ./Testing/benchmark.sh

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

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

18
.gitignore vendored
View File

@@ -1,9 +1,17 @@
.DS_Store
default.profraw
*.profraw
*.provisionprofile
bazel-*
Pods
Santa.xcodeproj/xcuserdata
Santa.xcodeproj/project.xcworkspace
Santa.xcworkspace/xcuserdata
Santa.xcworkspace/xcshareddata
Santa.xcodeproj/*
Santa.xcworkspace/*
CoverageData/*
*.tulsiconf-user
xcuserdata
tulsigen-*
*.crt
*.key
*.pem
*.p12
*.keychain
*.swp

View File

@@ -1,14 +0,0 @@
---
os: osx
osx_image: xcode11
language: objective-c
sudo: false
addons:
homebrew:
taps: bazelbuild/tap
packages: bazelbuild/tap/bazel
script:
- bazel build :release --show_progress_rate_limit=30.0 -c opt --apple_generate_dsym --color=no --verbose_failures --sandbox_debug
- bazel test :unit_tests --show_progress_rate_limit=30.0 --test_output=errors --color=no --verbose_failures --sandbox_debug

119
BUILD
View File

@@ -2,7 +2,7 @@ load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
package(default_visibility = ["//visibility:public"])
package(default_visibility = ["//:santa_package_group"])
licenses(["notice"])
@@ -15,12 +15,31 @@ apple_bundle_version(
short_version_string = SANTA_VERSION,
)
# Used to detect release builds
config_setting(
name = "release_build",
values = {"define": "SANTA_BUILD_TYPE=release"},
visibility = [":santa_package_group"],
)
# Used to detect CI builds
config_setting(
name = "ci_build",
values = {"define": "SANTA_BUILD_TYPE=ci"},
visibility = [":santa_package_group"],
)
# Used to detect optimized builds
config_setting(
name = "opt_build",
values = {"compilation_mode": "opt"},
)
package_group(
name = "santa_package_group",
packages = ["//..."],
)
################################################################################
# Loading/Unloading/Reloading
################################################################################
@@ -29,6 +48,7 @@ run_command(
cmd = """
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
sudo kextunload -b com.google.santa-driver 2>/dev/null
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
""",
@@ -39,6 +59,7 @@ run_command(
cmd = """
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
launchctl load /Library/LaunchAgents/com.google.santa.plist
""",
)
@@ -54,9 +75,9 @@ set -e
rm -rf /tmp/bazel_santa_reload
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
$${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-bin/Source/santa/Santa.zip >/dev/null
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa/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
@@ -72,15 +93,17 @@ genrule(
name = "release",
srcs = [
"//Source/santa:Santa",
"//Source/santa_driver",
"Conf/install.sh",
"Conf/uninstall.sh",
"Conf/com.google.santa.bundleservice.plist",
"Conf/com.google.santa.metricservice.plist",
"Conf/com.google.santad.plist",
"Conf/com.google.santa.plist",
"Conf/com.google.santa.asl.conf",
"Conf/com.google.santa.newsyslog.conf",
"Conf/Package/Makefile",
"Conf/Package/Distribution.xml",
"Conf/Package/notarization_tool.sh",
"Conf/Package/package_and_sign.sh",
"Conf/Package/postinstall",
"Conf/Package/preinstall",
],
@@ -91,9 +114,9 @@ genrule(
echo "Please add '-c opt' flag to bazel invocation"
""",
":opt_build": """
# Extract santa_driver.zip and Santa.zip
# Extract Santa.zip
for SRC in $(SRCS); do
if [ "$$(basename $${SRC})" == "santa_driver.zip" -o "$$(basename $${SRC})" == "Santa.zip" ]; then
if [ "$$(basename $${SRC})" == "Santa.zip" ]; then
mkdir -p $(@D)/binaries
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
fi
@@ -101,19 +124,15 @@ genrule(
# Copy config files
for SRC in $(SRCS); do
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
if [[ "$$(dirname $${SRC})" == *"Conf"* ]]; then
mkdir -p $(@D)/conf
cp $${SRC} $(@D)/conf/
cp -H $${SRC} $(@D)/conf/
fi
done
# 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
;;
*santad.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santad.dSYM
@@ -126,10 +145,18 @@ genrule(
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
;;
*santametricservice.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santametricservice.dSYM
;;
*Santa.app.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
;;
*com.google.santa.daemon.systemextension.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/com.google.santa.daemon.systemextension.dSYM
;;
esac
done
@@ -152,16 +179,76 @@ 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:SNTFileInfoTest",
"//Source/common:SNTMetricSetTest",
"//Source/common:SNTPrefixTreeTest",
"//Source/santa_driver:SantaCacheTest",
"//Source/santactl:SNTCommandFileInfoTest",
"//Source/santactl:SNTCommandSyncTest",
"//Source/common:SantaCacheTest",
"//Source/santactl:unit_tests",
"//Source/santad:SNTApplicationCoreMetricsTest",
"//Source/santad:SNTApplicationTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
"//Source/santad:SNTRuleTableTest",
"//Source/santametricservice:unit_tests",
],
)
test_suite(
name = "benchmarks",
tests = [
"//Source/santad:SNTApplicationBenchmark",
],
)

View File

@@ -1,37 +0,0 @@
Want to contribute? Great! First, read this page (including the small print at the end).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
Before you start working on a larger contribution, you should get in touch with
us first through the [issue tracker](https://github.com/google/santa/issues)
with your idea so that we can help out and possibly guide you. Coordinating up
front makes it much easier to avoid frustration later on.
### Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. It's also a good idea to run the
tests beforehand, which you can do with the following commands:
```sh
rake tests:logic
rake tests:kernel # only necessary if you're changing the kext code
```
### Code Style
All code submissions should try to match the surrounding code. Wherever possible,
code should adhere to either the
[Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.xml)
or the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).
### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).

1
CONTRIBUTING.md Symbolic link
View File

@@ -0,0 +1 @@
docs/development/contributing.md

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<installer-gui-script minSpecVersion="1">
<title>Santa</title>
<options customize="never" allow-external-scripts="no"/>
<choices-outline>
<line choice="default" />
</choices-outline>
<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

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

View File

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

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

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

View File

@@ -1,6 +1,6 @@
#!/bin/bash
# Load the kernel extension, santad, sync client
# Load com.google.santa.daemon and com.google.santa.bundleservice
# If a user is logged in, also load the GUI agent.
# If the target volume is not /, do nothing
@@ -13,24 +13,22 @@
mkdir -p /usr/local/bin
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
if [ $(uname -r | cut -d'.' -f1) -ge 19 ]; then
# Running on 10.15+
echo "Santa postinstall: running on 10.15+"
/bin/rm -rf /Library/Extensions/santa-driver.kext
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
else
# Running on <10.15
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
fi
# 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 the bundle service
# Load com.google.santa.daemon, its main has logic to handle loading the kext
# or relaunching itself as a SystemExtension.
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
# Load com.google.santa.bundleservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
user=$(/usr/bin/stat -f '%u' /dev/console)
if [[ -z "$user" ]]; then
/Applications/Santa.app/Contents/MacOS/Santa --load-system-extension
exit 0
fi
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santa.plist
# Load com.google.santa.metricservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
[[ -z "${GUI_USER}" ]] && exit 0
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl load /Library/LaunchAgents/com.google.santa.plist
exit 0

View File

@@ -8,6 +8,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/sleep 1
@@ -19,11 +20,13 @@
/bin/launchctl remove com.google.santasync
/bin/rm -f /Library/LaunchDaemons/com.google.santasync.plist
/bin/rm -rf /Applications/Santa.app
/bin/rm -rf /Library/Extensions/santa-driver.kext
/bin/sleep 1
user=$(/usr/bin/stat -f '%u' /dev/console)
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santa
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
[[ -z "${GUI_USER}" ]] && exit 0
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santagui
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santa
exit 0

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.google.santa.metricservice</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/santametricservice</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>com.google.santa.metricservice</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.google.santa.syncservice</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/santasyncservice</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>com.google.santa.syncservice</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@@ -24,6 +24,9 @@ fi
# Unload bundle service
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
# Unload metric service
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
@@ -31,10 +34,10 @@ fi
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Unload GUI agent if someone is logged in.
[[ -n "${GUI_USER}" ]] && \
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santagui
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove com.google.santagui
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove com.google.santa
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santa
# Cleanup cruft from old versions
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
@@ -49,33 +52,34 @@ 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.santad.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
# Reload syslogd to pick up ASL configuration change.
/usr/bin/killall -HUP syslogd
# Only copy the kext and load santad if running pre-10.15
if [ $(uname -r | cut -d'.' -f1) -lt 19 ]; then
/bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
else
/Applications/Santa.app/Contents/MacOS/Santa --load-system-extension
fi
# Load com.google.santa.daemon
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
# Load the bundle service
# Load com.google.santa.bundleservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load GUI agent if someone is logged in.
if [[ -n "$GUI_USER" ]]; then
/bin/launchctl asuser ${GUI_USER} \
/bin/launchctl load -w /Library/LaunchAgents/com.google.santa.plist
fi
# Load com.google.santa.metricservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load GUI agent if someone is logged in.
[[ -z "${GUI_USER}" ]] && exit 0
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl load -w /Library/LaunchAgents/com.google.santa.plist
exit 0

View File

@@ -7,7 +7,7 @@
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
# For macOS 10.15+ this will block up to 60 seconds
/Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
/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
sleep 1
@@ -24,6 +24,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
/bin/rm -f /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

@@ -14,10 +14,11 @@
#include <SantaCache.h>
#include <iostream>
#include <cstdint>
#include <iostream>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data,
std::size_t size) {
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
std::uint64_t fields[2] = {};
@@ -33,7 +34,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
auto returned_value = decision_cache.get(fields[0]);
if (returned_value != fields[1]) {
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value << "\n";
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value
<< "\n";
return 1;
}

View File

@@ -12,13 +12,13 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#include <iostream>
#include <vector>
#include <SNTCommandSyncConstants.h>
#include <SNTCommandSyncRuleDownload.h>
#include <SNTCommandSyncState.h>
#include <SNTCommandSyncConstants.h>
#include <SNTRule.h>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
@@ -57,6 +57,6 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
}
}
return 0;
}

View File

@@ -12,8 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#include <iostream>
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -23,15 +23,14 @@
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
if (size > 16) {
std::cerr << "Invalid buffer size of " << size
<< " (should be <= 16)" << std::endl;
std::cerr << "Invalid buffer size of " << size << " (should be <= 16)" << std::endl;
return 1;
}
santa_vnode_id_t vnodeID = {};
std::memcpy(&vnodeID, data, size);
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
@@ -40,16 +39,20 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
[daemonConn resume];
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;;
} else if (action == ACTION_RESPOND_DENY) {
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;;
} else if (action == ACTION_UNSET) {
std::cerr << "File does not exist in cache" << std::endl;;
}
}];
[[daemonConn remoteObjectProxy]
checkCacheForVnodeID:vnodeID
withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;
;
} else if (action == ACTION_RESPOND_DENY) {
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;
;
} else if (action == ACTION_UNSET) {
std::cerr << "File does not exist in cache" << std::endl;
;
}
}];
return 0;
}

View File

@@ -12,8 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#include <iostream>
#import <MOLXPCConnection/MOLXPCConnection.h>

View File

@@ -12,8 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#include <iostream>
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -34,9 +34,8 @@ struct InputData {
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
if (size > sizeof(InputData)) {
std::cerr << "Invalid buffer size of " << size
<< " (should be <= " << sizeof(InputData)
<< ")" << std::endl;
std::cerr << "Invalid buffer size of " << size << " (should be <= " << sizeof(InputData) << ")"
<< std::endl;
return 1;
}
@@ -45,11 +44,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
std::memcpy(&input_data, data, size);
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = (SNTRuleState) input_data.state;
newRule.type = (SNTRuleType) input_data.type;
newRule.shasum = @(input_data.hash);
newRule.state = (SNTRuleState)input_data.state;
newRule.type = (SNTRuleType)input_data.type;
newRule.identifier = @(input_data.hash);
newRule.customMsg = @"";
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
@@ -57,17 +56,18 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
};
[daemonConn resume];
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
cleanSlate:NO
reply:^(NSError *error) {
if (!error) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
}
}
}];
[[daemonConn remoteObjectProxy]
databaseRuleAddRules:@[ newRule ]
cleanSlate:NO
reply:^(NSError *error) {
if (!error) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
}
}
}];
return 0;
}

22
Podfile
View File

@@ -1,22 +0,0 @@
def common_pods
pod 'MOLXPCConnection'
pod 'MOLCodesignChecker'
pod 'FMDB'
pod 'MOLCertificate'
pod 'OCMock'
pod 'MOLAuthenticatingURLSession'
pod 'MOLFCMClient'
end
project './Santa.xcodeproj'
project = Xcodeproj::Project.open "./Santa.xcodeproj"
project.targets.each do |t|
if t.name == "santa-driver"
next
end
target t.name do
common_pods
end
end

View File

@@ -1,46 +0,0 @@
PODS:
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- MOLAuthenticatingURLSession (2.4):
- MOLCertificate (~> 1.8)
- MOLCertificate (1.9)
- MOLCodesignChecker (1.10):
- MOLCertificate (~> 1.8)
- MOLFCMClient (1.8):
- MOLAuthenticatingURLSession (~> 2.4)
- MOLXPCConnection (1.2):
- MOLCodesignChecker (~> 1.9)
- OCMock (3.5)
DEPENDENCIES:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient
- MOLXPCConnection
- OCMock
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient
- MOLXPCConnection
- OCMock
SPEC CHECKSUMS:
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
MOLFCMClient: 2bfbacd45cc11e1ca3c077e97b80401c4e4a54f1
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
OCMock: 4ab4577fc941af31f4a0398f6e7e230cf21fc72a
PODFILE CHECKSUM: d03767a9915896232523962c98d9ff7294aec2b7
COCOAPODS: 1.7.5

128
README.md
View File

@@ -1,29 +1,23 @@
# Santa [![Build Status][build-status-img]][build-status-link] [![Documentation Status][doc-status-img]][doc-status-link]
[build-status-img]: https://travis-ci.org/google/santa.png?branch=master
[build-status-link]: https://travis-ci.org/google/santa
[doc-status-img]: https://readthedocs.org/projects/santa/badge/?version=latest
[doc-status-link]: https://santa.readthedocs.io/en/latest/?badge=latest
# Santa [![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/google/santa/badge.svg?branch=main)](https://coveralls.io/github/google/santa?branch=main)
<p align="center">
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
</p>
Santa is a binary whitelisting/blacklisting system for macOS. It consists of a
kernel extension (or a system extension on macOS 10.15+) that monitors for executions, a userland daemon that makes
execution decisions based on the contents of a SQLite database, a GUI agent
that notifies the user in case of a block decision and a command-line utility
for managing the system and synchronizing the database with a server.
Santa is a binary authorization system for macOS. It consists of a system 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.
It is named Santa because it keeps track of binaries that are naughty or nice.
Santa is a project of Google's Macintosh Operations Team.
# Docs
The Santa docs are stored in the
[Docs](https://github.com/google/santa/blob/master/docs) directory. A Read the
Docs instance is available here: https://santa.readthedocs.io.
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
at http://santa.dev.
The docs include deployment options, details on how parts of Santa work and
instructions for developing Santa itself.
@@ -35,29 +29,32 @@ the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
great place.
If you believe you have a bug, feel free to report [an
issue](https://github.com/google/santa/isues) and we'll respond as soon as we
issue](https://github.com/google/santa/issues) and we'll respond as soon as we
can.
If you believe you've found a vulnerability, please read the
[security policy](https://github.com/google/santa/security/policy) for
disclosure reporting.
# Admin-Related Features
# Features
* Multiple modes: In the default MONITOR mode, all binaries except those marked
as blacklisted will be allowed to run, whilst being logged and recorded in
the events database. In LOCKDOWN mode, only whitelisted binaries are allowed
to run.
as blocked will be allowed to run, whilst being logged and recorded in
the events database. In LOCKDOWN mode, only listed binaries are allowed to
run.
* Event logging: When the kext is loaded, all binary launches are logged. When
in either mode, all unknown or denied binaries are stored in the database to
enable later aggregation.
* Certificate-based rules, with override levels: Instead of relying on a
binary's hash (or 'fingerprint'), executables can be whitelisted/blacklisted
by their signing certificate. You can therefore trust/block all binaries by a
binary's hash (or 'fingerprint'), executables can be allowed/blocked by their
signing certificate. You can therefore allow/block all binaries by a
given publisher that were signed with that cert across version updates. A
binary can only be whitelisted by its certificate if its signature validates
correctly, but a rule for a binary's fingerprint will override a decision for
a certificate; i.e. you can whitelist a certificate while blacklisting a
binary signed with that certificate, or vice-versa.
binary can only be allowed by its certificate if its signature validates
correctly but a rule for a binary's fingerprint will override a decision for
a certificate; i.e. you can allowlist a certificate while blocking a binary
signed with that certificate, or vice-versa.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature
to that found in Managed Client (the precursor to configuration profiles,
@@ -70,10 +67,18 @@ can.
* Failsafe cert rules: You cannot put in a deny rule that would block the
certificate used to sign launchd, a.k.a. pid 1, and therefore all components
used in macOS. The binaries in every OS update (and in some cases entire new
versions) are therefore auto-whitelisted. This does not affect binaries from
Apple's App Store, which use various certs that change regularly for common
apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct
separate cert than other Google apps.
versions) are therefore automatically allowed. This does not affect binaries
from Apple's App Store, which use various certs that change regularly for
common apps. Likewise, you cannot block Santa itself, and Santa uses a
distinct separate cert than other Google apps.
* Userland components validate each other: each of the userland components (the
daemon, the GUI agent and the command-line utility) communicate with each
other using XPC and check that their signing certificates are identical
before any communication is accepted.
* Caching: allowed binaries are cached so the processing required to make a
request is only done if the binary isn't already cached.
# Intentions and Expectations
@@ -90,42 +95,17 @@ protect hosts in whatever other ways you see fit.
# Security and Performance-Related Features
* In-kernel caching: whitelisted binaries are cached in the kernel so the
processing required to make a request is only done if the binary isn't
already cached.
* Userland components validate each other: each of the userland components (the
daemon, the GUI agent and the command-line utility) communicate with each
other using XPC and check that their signing certificates are identical
before any communication is accepted.
* Kext uses only KPIs: the kernel extension only uses provided kernel
programming interfaces to do its job. This means that the kext code should
continue to work across OS versions.
# Known Issues
* Santa only blocks execution (execve and variants), it doesn't protect against
dynamic libraries loaded with dlopen, libraries on disk that have been
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version
0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were
exploited in some versions of macOS. We are working on also protecting
against similar avenues of attack.
* Kext communication security: the kext will only accept a connection from a
single client at a time and said client must be running as root. We haven't
yet found a good way to ensure the kext only accepts connections from a valid
client.
* Database protection: the SQLite database is installed with permissions so
that only the root user can read/write it. We're considering approaches to
secure this further.
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`.
* Scripts: Santa is currently written to ignore any execution that isn't a
binary. This is because after weighing the administration cost vs the
benefit, we found it wasn't worthwhile. Additionally, a number of
applications make use of temporary generated scripts, which we can't possibly
whitelist and not doing so would cause problems. We're happy to revisit this
allowlist and not doing so would cause problems. We're happy to revisit this
(or at least make it an option) if it would be useful to others.
# Sync Servers
@@ -134,13 +114,17 @@ protect hosts in whatever other ways you see fit.
management server, which uploads events that have occurred on the machine and
downloads new rules. There are several open-source servers you can sync with:
* [Upvote](https://github.com/google/upvote) - An AppEngine-based server
that implements social voting to make managing a large fleet easier.
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
serves hardcoded rules from simple configuration files.
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
centralized service that pulls data from multiple sources and deploy
configurations to multiple services.
* [Zercurity](https://github.com/zercurity/zercurity) - A dockerized service
for managing and monitoring applications across a large fleet utilizing
Santa + Osquery.
* Alternatively, `santactl` can configure rules locally (without a sync
server).
@@ -150,34 +134,12 @@ protect hosts in whatever other ways you see fit.
A tool like Santa doesn't really lend itself to screenshots, so here's a video
instead.
<p align="center"> <img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif"
alt="Santa Block Video" /> </p>
# Kext Signing
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
Developer ID certificate with a kernel extension flag. Without it, the only way
to load an extension is to enable kext-dev-mode or disable SIP, depending on
the OS version.
There are two possible solutions for this, for distribution purposes:
1) Use a [pre-built, pre-signed
version](https://github.com/google/santa/releases) of the kext that we supply.
Each time changes are made to the kext code we will update the pre-built
version that you can make use of. This doesn't prevent you from making changes
to the non-kext parts of Santa and distributing those. If you make changes to
the kext and make a pull request, we can merge them in and distribute a new
version of the pre-signed kext.
2) Apply for your own [kext signing
certificate](https://developer.apple.com/contact/kext/). Apple will only grant
this for broad distribution within an organization, they won't issue them just
for testing purposes.
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
# Contributing
Patches to this project are very much welcome. Please see the
[CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
file.
[CONTRIBUTING](https://santa.dev/development/contributing) doc.
# Disclaimer
This is **not** an official Google product.

12
SECURITY.md Normal file
View File

@@ -0,0 +1,12 @@
# 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
disclosed publicly either when a new version with fixes is released or 90 days has passed,
whichever comes first.
To report vulnerabilities to us privately, please e-mail `santa-team@google.com`.
If you want to encrypt your e-mail, you can use our GPG key `0x92AFE41DAB49BBB6`
available on pool.sks-keyservers.net:
`gpg --keyserver pool.sks-keyservers.net --recv-key 0x92AFE41DAB49BBB6`

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
BuildableName = "Santa.app"
BlueprintName = "Santa"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
BuildableName = "Santa.app"
BlueprintName = "Santa"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
BuildableName = "Santa.app"
BlueprintName = "Santa"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
BuildableName = "com.google.santa.daemon"
BlueprintName = "com.google.santa.daemon"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
debugAsWhichUser = "root"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
BuildableName = "com.google.santa.daemon"
BlueprintName = "com.google.santa.daemon"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
BuildableName = "com.google.santa.daemon"
BlueprintName = "com.google.santa.daemon"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Santa.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -1,15 +1,31 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"]) # Apache 2.0
load("//:helper.bzl", "santa_unit_test")
package(default_visibility = ["//:santa_package_group"])
licenses(["notice"])
cc_library(
name = "SantaCache",
hdrs = ["SantaCache.h"],
deps = ["//Source/common:SNTKernelCommon"],
)
santa_unit_test(
name = "SantaCacheTest",
srcs = [
"SantaCache.h",
"SantaCacheTest.mm",
],
deps = ["//Source/common:SNTKernelCommon"],
)
objc_library(
name = "SNTBlockMessage",
srcs = ["SNTBlockMessage.m"],
hdrs = ["SNTBlockMessage.h"],
deps = [
":SNTConfigurator",
":SNTLogging",
":SNTStoredEvent",
],
)
@@ -21,6 +37,7 @@ objc_library(
defines = ["SANTAGUI"],
deps = [
":SNTConfigurator",
":SNTLogging",
":SNTStoredEvent",
],
)
@@ -35,7 +52,7 @@ objc_library(
],
)
cc_library(
objc_library(
name = "SNTCommonEnums",
hdrs = ["SNTCommonEnums.h"],
)
@@ -46,7 +63,6 @@ objc_library(
hdrs = ["SNTConfigurator.h"],
deps = [
":SNTCommonEnums",
":SNTLogging",
":SNTStrengthify",
":SNTSystemInfo",
],
@@ -71,17 +87,31 @@ objc_library(
cc_library(
name = "SNTKernelCommon",
hdrs = ["SNTKernelCommon.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"],
hdrs = ["SNTLogging.h"],
deps = [":SNTConfigurator"],
)
cc_library(
@@ -101,7 +131,11 @@ cc_library(
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = ["KERNEL"],
defines = [
"KERNEL",
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
deps = [":SNTLoggingKernel"],
)
@@ -144,6 +178,15 @@ objc_library(
],
)
objc_library(
name = "SNTXPCMetricServiceInterface",
srcs = ["SNTXPCMetricServiceInterface.m"],
hdrs = ["SNTXPCMetricServiceInterface.h"],
deps = [
"@MOLXPCConnection",
],
)
objc_library(
name = "SNTXPCControlInterface",
srcs = ["SNTXPCControlInterface.m"],
@@ -166,6 +209,13 @@ objc_library(
],
)
objc_library(
name = "SNTMetricSet",
srcs = ["SNTMetricSet.m"],
hdrs = ["SNTMetricSet.h"],
deps = [":SNTCommonEnums"],
)
objc_library(
name = "SNTXPCSyncdInterface",
srcs = ["SNTXPCSyncdInterface.m"],
@@ -176,6 +226,16 @@ objc_library(
],
)
objc_library(
name = "SNTXPCSyncServiceInterface",
srcs = ["SNTXPCSyncServiceInterface.m"],
hdrs = ["SNTXPCSyncServiceInterface.h"],
deps = [
":SNTStoredEvent",
"@MOLXPCConnection",
],
)
objc_library(
name = "SNTXPCUnprivilegedControlInterface",
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
@@ -195,6 +255,7 @@ santa_unit_test(
name = "SNTFileInfoTest",
srcs = ["SNTFileInfoTest.m"],
resources = [
"testdata/32bitplist",
"testdata/bad_pagezero",
"testdata/missing_pagezero",
],
@@ -208,5 +269,11 @@ santa_unit_test(
santa_unit_test(
name = "SNTPrefixTreeTest",
srcs = ["SNTPrefixTreeTest.mm"],
deps = ["SNTPrefixTree"],
deps = [":SNTPrefixTree"],
)
santa_unit_test(
name = "SNTMetricSetTest",
srcs = ["SNTMetricSetTest.m"],
deps = [":SNTMetricSet"],
)

View File

@@ -17,31 +17,33 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTSystemInfo.h"
@implementation SNTBlockMessage
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage {
NSString *htmlHeader = @"<html><head><style>"
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: %@;"
@" text-align: center;"
@"}"
NSString *htmlHeader =
@"<html><head><style>"
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: %@;"
@" text-align: center;"
@"}"
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
@"@media (prefers-color-scheme: dark) {"
@" body {"
@" color: #ddd;"
@" }"
@"}"
@"</style></head><body>";
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
@"@media (prefers-color-scheme: dark) {"
@" body {"
@" color: #ddd;"
@" }"
@"}"
@"</style></head><body>";
// Support Dark Mode. Note, the returned NSAttributedString is static and does not update when
// the OS switches modes.
NSString *mode = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"];
BOOL dark = [mode isEqualToString:@"Dark"];
BOOL dark = [mode isEqualToString:@"Dark"];
htmlHeader = [NSString stringWithFormat:htmlHeader, dark ? @"#ddd" : @"#333"];
NSString *htmlFooter = @"</body></html>";
@@ -89,13 +91,14 @@
// Strip any HTML tags out of the message. Also remove any content inside <style> tags and
// replace <br> elements with a newline.
NSString *stripXslt = @"<?xml version='1.0' encoding='utf-8'?>"
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
@"<xsl:output method='text'/>"
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
@"<xsl:template match='style'/>"
@"</xsl:stylesheet>";
NSString *stripXslt =
@"<?xml version='1.0' encoding='utf-8'?>"
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
@"<xsl:output method='text'/>"
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
@"<xsl:template match='style'/>"
@"</xsl:stylesheet>";
NSData *data = [xml objectByApplyingXSLTString:stripXslt arguments:NULL error:&error];
if (error || ![data isKindOfClass:[NSData class]]) {
return html;
@@ -106,13 +109,16 @@
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *hostname = [SNTSystemInfo longHostname];
NSString *uuid = [SNTSystemInfo hardwareUUID];
NSString *serial = [SNTSystemInfo serialNumber];
NSString *formatStr = config.eventDetailURL;
if (!formatStr.length) return nil;
if (event.fileSHA256) {
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
}
if (event.executingUser) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
@@ -122,6 +128,15 @@
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
withString:config.machineID];
}
if (hostname.length) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%hostname%" withString:hostname];
}
if (uuid.length) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%uuid%" withString:uuid];
}
if (serial.length) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%" withString:serial];
}
return [NSURL URLWithString:formatStr];
}

View File

@@ -32,6 +32,7 @@
@property NSString *certSHA256;
@property NSString *certCommonName;
@property NSArray<MOLCertificate *> *certChain;
@property NSString *teamID;
@property NSString *quarantineURL;

View File

@@ -24,18 +24,19 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {
SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
SNTRuleTypeTeamID = 3,
};
typedef NS_ENUM(NSInteger, SNTRuleState) {
SNTRuleStateUnknown,
SNTRuleStateWhitelist = 1,
SNTRuleStateBlacklist = 2,
SNTRuleStateSilentBlacklist = 3,
SNTRuleStateAllow = 1,
SNTRuleStateBlock = 2,
SNTRuleStateSilentBlock = 3,
SNTRuleStateRemove = 4,
SNTRuleStateWhitelistCompiler = 5,
SNTRuleStateWhitelistTransitive = 6,
SNTRuleStateAllowCompiler = 5,
SNTRuleStateAllowTransitive = 6,
};
typedef NS_ENUM(NSInteger, SNTClientMode) {
@@ -55,6 +56,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateBlockBinary = 1 << 17,
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
SNTEventStateBlockTeamID = 1 << 20,
// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
@@ -64,6 +66,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
SNTEventStateAllowTeamID = 1 << 31,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
@@ -91,7 +94,15 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
SNTEventLogTypeFilelog,
};
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeUnknown,
SNTMetricFormatTypeRawJSON,
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 *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

@@ -36,32 +36,32 @@
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
///
/// The regex of whitelisted paths. Regexes are specified in ICU format.
/// The regex of allowed paths. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(readonly, nonatomic) NSRegularExpression *whitelistPathRegex;
@property(readonly, nonatomic) NSRegularExpression *allowedPathRegex;
///
/// Set the regex of whitelisted paths as received from a sync server.
/// Set the regex of allowed paths as received from a sync server.
///
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re;
- (void)setSyncServerAllowedPathRegex:(NSRegularExpression *)re;
///
/// The regex of blacklisted paths. Regexes are specified in ICU format.
/// The regex of blocked paths. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(readonly, nonatomic) NSRegularExpression *blacklistPathRegex;
@property(readonly, nonatomic) NSRegularExpression *blockedPathRegex;
///
/// Set the regex of blacklisted paths as received from a sync server.
/// Set the regex of blocked paths as received from a sync server.
///
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re;
- (void)setSyncServerBlockedPathRegex:(NSRegularExpression *)re;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
@@ -133,7 +133,7 @@
///
/// Enable bad signature protection, defaults to NO.
/// When enabled, a binary that is signed but has a bad signature (cert revoked, binary
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary whitelist
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary allowlist
/// rule exists.
///
@property(readonly, nonatomic) BOOL enableBadSignatureProtection;
@@ -168,13 +168,29 @@
/// 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.
/// 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
///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
///
@property(readonly, nonatomic) NSString *aboutText;
///
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
/// If unset, the button will not be displayed.
@@ -191,6 +207,9 @@
/// %file_sha% -- SHA-256 of the file that was blocked.
/// %machine_id% -- ID of the machine.
/// %username% -- executing user.
/// %serial% -- System's serial number.
/// %uuid% -- System's UUID.
/// %hostname% -- System's full hostname.
///
/// @note: This is not an NSURL because the format-string parsing is done elsewhere.
///
@@ -266,14 +285,14 @@
///
@property BOOL enableBundles;
#pragma mark Transitive Whitelisting Settings
#pragma mark Transitive Allowlist Settings
///
/// If YES, binaries marked with SNTRuleStateWhitelistCompiler rules are allowed to transitively
/// whitelist any executables that they produce. If NO, SNTRuleStateWhitelistCompiler rules are
/// interpreted as if they were simply SNTRuleStateWhitelist rules. Defaults to NO.
/// If YES, binaries marked with SNTRuleStateAllowCompiler rules are allowed to transitively
/// allow any executables that they produce. If NO, SNTRuleStateAllowCompiler rules are
/// interpreted as if they were simply SNTRuleStateAllow rules. Defaults to NO.
///
@property BOOL enableTransitiveWhitelisting;
@property BOOL enableTransitiveRules;
#pragma mark Server Auth Settings
@@ -312,6 +331,77 @@
///
@property(readonly, nonatomic) NSString *syncClientAuthCertificateIssuer;
///
/// If true, forks and exits will be logged. Defaults to false.
///
@property(readonly, nonatomic) BOOL enableForkAndExitLogging;
///
/// If true, ignore actions from other endpoint security clients. Defaults to false. This only
/// applies when running as a sysx.
///
@property(readonly, nonatomic) BOOL ignoreOtherEndpointSecurityClients;
///
/// If true, debug logging will be enabled for all Santa components. Defaults to false.
/// Passing --debug as an executable argument will enable debug logging for that specific
/// component.
///
@property(readonly, nonatomic) BOOL enableDebugLogging;
///
/// If true, compressed requests from "santactl sync" will set "Content-Encoding" to "zlib"
/// instead of the new default "deflate". If syncing with Upvote deployed at commit 0b4477d
/// or below, set this option to true.
/// Defaults to false.
///
@property(readonly, nonatomic) BOOL enableBackwardsCompatibleContentEncoding;
///
/// Contains the FCM project name.
///
@property(readonly, nonatomic) NSString *fcmProject;
///
/// Contains the FCM project entity.
///
@property(readonly, nonatomic) NSString *fcmEntity;
///
/// Contains the FCM project API key.
///
@property(readonly, nonatomic) NSString *fcmAPIKey;
///
/// True if fcmProject, fcmEntity and fcmAPIKey are all set. Defaults to false.
///
@property(readonly, nonatomic) BOOL fcmEnabled;
///
/// True if metricsFormat and metricsURL are set. False otherwise.
///
@property(readonly, nonatomic) BOOL exportMetrics;
///
/// Format to export Metrics as.
///
@property(readonly, nonatomic) SNTMetricFormatType metricFormat;
///
/// URL describing where metrics are exported, defaults to nil.
///
@property(readonly, nonatomic) NSURL *metricURL;
///
/// Extra Metric Labels to add to the metrics payloads.
///
@property(readonly, nonatomic) NSDictionary *extraMetricLabels;
///
/// Duration in seconds of how often the metrics should be exported.
///
@property(readonly, nonatomic) NSUInteger metricExportInterval;
///
/// Retrieve an initialized singleton configurator object using the default file path.
///

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
#include <sys/stat.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTSystemInfo.h"
@@ -24,13 +23,16 @@
/// A NSUserDefaults object set to use the com.google.santa suite.
@property(readonly, nonatomic) NSUserDefaults *defaults;
// Keys and expected value types.
/// Keys and expected value types.
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
/// Holds the configurations from a sync server and mobileconfig.
@property NSMutableDictionary *syncState;
@property NSMutableDictionary *configState;
/// Was --debug passed as an argument to this process?
@property(readonly, nonatomic) BOOL debugFlag;
@end
@implementation SNTConfigurator
@@ -57,6 +59,7 @@ static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kAboutText = @"AboutText";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
static NSString *const kEventDetailTextKey = @"EventDetailText";
@@ -77,12 +80,33 @@ static NSString *const kEventLogPath = @"EventLogPath";
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";
static NSString *const kEnableBackwardsCompatibleContentEncoding =
@"EnableBackwardsCompatibleContentEncoding";
static NSString *const kFCMProject = @"FCMProject";
static NSString *const kFCMEntity = @"FCMEntity";
static NSString *const kFCMAPIKey = @"FCMAPIKey";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kEnableTransitiveWhitelistingKey = @"EnableTransitiveWhitelisting";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting";
static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
static NSString *const kAllowedPathRegexKeyDeprecated = @"WhitelistRegex";
static NSString *const kBlockedPathRegexKey = @"BlockedPathRegex";
static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
// 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 kMetricExtraLabels = @"MetricExtraLabels";
// The keys managed by a sync server.
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
@@ -98,24 +122,32 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
Class string = [NSString class];
Class data = [NSData class];
Class array = [NSArray class];
Class dictionary = [NSDictionary class];
_syncServerKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveWhitelistingKey : number,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kEnableTransitiveRulesKey : number,
kEnableTransitiveRulesKeyDeprecated : number,
kAllowedPathRegexKey : re,
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveWhitelistingKey : number,
kEnableTransitiveRulesKey : number,
kEnableTransitiveRulesKeyDeprecated : number,
kFileChangesRegexKey : re,
kFileChangesPrefixFiltersKey : array,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kAllowedPathRegexKey : re,
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey: number,
kEnableBadSignatureProtectionKey : number,
kAboutText : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
@@ -128,7 +160,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
kClientAuthCertificateIssuerKey : string,
kServerAuthRootsDataKey : data,
kServerAuthRootsDataKey : data,
kServerAuthRootsFileKey : string,
kMachineOwnerKey : string,
kMachineIDKey : string,
@@ -140,11 +172,24 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kEventLogPath : string,
kEnableMachineIDDecoration : number,
kEnableSystemExtension : number,
kEnableSysxCache : number,
kEnableForkAndExitLogging : number,
kIgnoreOtherEndpointSecurityClients : number,
kEnableDebugLogging : number,
kEnableBackwardsCompatibleContentEncoding : number,
kFCMProject : string,
kFCMEntity : string,
kFCMAPIKey : string,
kMetricFormat : string,
kMetricURL : string,
kMetricExportInterval : number,
kMetricExtraLabels : dictionary,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
_configState = [self readForcedConfig];
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
_debugFlag = [[NSProcessInfo processInfo].arguments containsObject:@"--debug"];
[self startWatchingDefaults];
}
return self;
@@ -194,11 +239,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
+ (NSSet *)keyPathsForValuesAffectingAllowlistPathRegex {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
+ (NSSet *)keyPathsForValuesAffectingBlocklistPathRegex {
return [self syncAndConfigStateSet];
}
@@ -218,6 +263,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingAboutText {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [self configStateSet];
}
@@ -302,7 +351,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableTransitiveWhitelisting {
+ (NSSet *)keyPathsForValuesAffectingEnableTransitiveRules {
return [self syncAndConfigStateSet];
}
@@ -310,6 +359,46 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableForkAndExitLogging {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingIgnoreOtherEndpointSecurityClients {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableDebugLogging {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmEntity {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmAPIKey {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmEnabled {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBadSignatureProtection {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -329,37 +418,58 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
} else {
LOGW(@"Ignoring request to change client mode to %ld", newMode);
}
}
- (BOOL)enableTransitiveWhitelisting {
NSNumber *n = self.syncState[kEnableTransitiveWhitelistingKey];
if (n) {
return [n boolValue];
}
return [self.configState[kEnableTransitiveWhitelistingKey] boolValue];
- (BOOL)enableTransitiveRules {
NSNumber *n = self.syncState[kEnableTransitiveRulesKey];
if (n) return [n boolValue];
n = self.syncState[kEnableTransitiveRulesKeyDeprecated];
if (n) return [n boolValue];
n = self.configState[kEnableTransitiveRulesKeyDeprecated];
if (n) return [n boolValue];
return [self.configState[kEnableTransitiveRulesKey] boolValue];
}
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled {
[self updateSyncStateForKey:kEnableTransitiveWhitelistingKey value:@(enabled)];
- (void)setEnableTransitiveRules:(BOOL)enabled {
[self updateSyncStateForKey:kEnableTransitiveRulesKey value:@(enabled)];
}
- (NSRegularExpression *)whitelistPathRegex {
return self.syncState[kWhitelistRegexKey] ?: self.configState[kWhitelistRegexKey];
- (NSRegularExpression *)allowedPathRegex {
NSRegularExpression *r = self.syncState[kAllowedPathRegexKey];
if (r) return r;
r = self.syncState[kAllowedPathRegexKeyDeprecated];
if (r) return r;
r = self.configState[kAllowedPathRegexKey];
if (r) return r;
return self.configState[kAllowedPathRegexKeyDeprecated];
}
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kWhitelistRegexKey value:re];
- (void)setSyncServerAllowedPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kAllowedPathRegexKey value:re];
}
- (NSRegularExpression *)blacklistPathRegex {
return self.syncState[kBlacklistRegexKey] ?: self.configState[kBlacklistRegexKey];
- (NSRegularExpression *)blockedPathRegex {
NSRegularExpression *r = self.syncState[kBlockedPathRegexKey];
if (r) return r;
r = self.syncState[kBlockedPathRegexKeyDeprecated];
if (r) return r;
r = self.configState[kBlockedPathRegexKey];
if (r) return r;
return self.configState[kBlockedPathRegexKeyDeprecated];
}
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kBlacklistRegexKey value:re];
- (void)setSyncServerBlockedPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kBlockedPathRegexKey value:re];
}
- (NSRegularExpression *)fileChangesRegex {
@@ -370,7 +480,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
NSArray *filters = self.configState[kFileChangesPrefixFiltersKey];
for (id filter in filters) {
if (![filter isKindOfClass:[NSString class]]) {
LOGE(@"Ignoring FileChangesPrefixFilters: array contains a non-string %@", filter);
return nil;
}
}
@@ -381,7 +490,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
NSURL *url = [NSURL URLWithString:urlString];
if (urlString && !url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
}
@@ -395,6 +503,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : NO;
}
- (NSString *)aboutText {
return self.configState[kAboutText];
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
}
@@ -526,6 +638,88 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
- (BOOL)enableSysxCache {
NSNumber *number = self.configState[kEnableSysxCache];
return number ? [number boolValue] : YES;
}
- (BOOL)enableForkAndExitLogging {
NSNumber *number = self.configState[kEnableForkAndExitLogging];
return number ? [number boolValue] : NO;
}
- (BOOL)ignoreOtherEndpointSecurityClients {
NSNumber *number = self.configState[kIgnoreOtherEndpointSecurityClients];
return number ? [number boolValue] : NO;
}
- (BOOL)enableDebugLogging {
NSNumber *number = self.configState[kEnableDebugLogging];
return [number boolValue] || self.debugFlag;
}
- (BOOL)enableBackwardsCompatibleContentEncoding {
NSNumber *number = self.configState[kEnableBackwardsCompatibleContentEncoding];
return number ? [number boolValue] : NO;
}
- (NSString *)fcmProject {
return self.configState[kFCMProject];
}
- (NSString *)fcmEntity {
return self.configState[kFCMEntity];
}
- (NSString *)fcmAPIKey {
return self.configState[kFCMAPIKey];
}
- (BOOL)fcmEnabled {
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
}
///
/// Returns YES if all of the necessary options are set to export metrics, NO
/// otherwise.
///
- (BOOL)exportMetrics {
return [self metricFormat] != SNTMetricFormatTypeUnknown &&
![self.configState[kMetricURL] isEqualToString:@""];
}
- (SNTMetricFormatType)metricFormat {
NSString *normalized = [self.configState[kMetricFormat] lowercaseString];
normalized = [normalized stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([normalized isEqualToString:@"rawjson"]) {
return SNTMetricFormatTypeRawJSON;
} else if ([normalized isEqualToString:@"monarchjson"]) {
return SNTMetricFormatTypeMonarchJSON;
} else {
return SNTMetricFormatTypeUnknown;
}
}
- (NSURL *)metricURL {
return [NSURL URLWithString:self.configState[kMetricURL]];
}
// Returns a default value of 30 (for 30 seconds).
- (NSUInteger)metricExportInterval {
NSNumber *configuredInterval = self.configState[kMetricExportInterval];
if (configuredInterval == nil) {
return 30;
}
return [configuredInterval unsignedIntegerValue];
}
- (NSDictionary *)extraMetricLabels {
return self.configState[kMetricExtraLabels];
}
#pragma mark Private
///
@@ -549,7 +743,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
// Only santad should read this file.
if (geteuid() != 0) return nil;
NSMutableDictionary *syncState =
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
for (NSString *key in syncState.allKeys) {
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
@@ -572,11 +766,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
if (geteuid() != 0) return;
// Either remove
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[kWhitelistRegexKey] = [syncState[kWhitelistRegexKey] pattern];
syncState[kBlacklistRegexKey] = [syncState[kBlacklistRegexKey] pattern];
syncState[kAllowedPathRegexKey] = [syncState[kAllowedPathRegexKey] pattern];
syncState[kBlockedPathRegexKey] = [syncState[kBlockedPathRegexKey] pattern];
[syncState writeToFile:kSyncStateFilePath atomically:YES];
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
ofItemAtPath:kSyncStateFilePath error:NULL];
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0644}
ofItemAtPath:kSyncStateFilePath
error:NULL];
}
- (void)clearSyncState {
@@ -611,8 +806,9 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
- (void)startWatchingDefaults {
// Only santad should listen.
if (geteuid() != 0) return;
// Only com.google.santa.daemon should listen.
NSString *processName = [[NSProcessInfo processInfo] processName];
if (![processName isEqualToString:@"com.google.santa.daemon"]) return;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification

View File

@@ -40,7 +40,6 @@
///
- (instancetype)initWithPath:(NSString *)path;
///
/// Initializer for already resolved paths.
///

View File

@@ -15,8 +15,8 @@
#import "Source/common/SNTFileInfo.h"
#import <CommonCrypto/CommonDigest.h>
#import <fmdb/FMDB.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import <fmdb/FMDB.h>
#include <mach-o/arch.h>
#include <mach-o/loader.h>
@@ -25,7 +25,6 @@
#include <sys/stat.h>
#include <sys/xattr.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
@@ -181,30 +180,27 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(digest, &c1);
NSString *const SHA1FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha1 = [[NSString alloc]
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19]];
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19]];
}
if (sha256) {
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &c256);
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha256 = [[NSString alloc]
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19], digest[20],
digest[21], digest[22], digest[23], digest[24],
digest[25], digest[26], digest[27], digest[28],
digest[29], digest[30], digest[31]];
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19], digest[20], digest[21], digest[22],
digest[23], digest[24], digest[25], digest[26], digest[27], digest[28],
digest[29], digest[30], digest[31]];
}
} @finally {
free(chunk);
@@ -292,15 +288,15 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (BOOL)isMissingPageZero {
// This method only checks i386 arch because the kernel enforces this for other archs
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86
cpuSubType:CPU_SUBTYPE_I386_ALL]];
MachHeaderWithOffset *x86Header =
self.machHeaders[[self nameForCPUType:CPU_TYPE_X86 cpuSubType:CPU_SUBTYPE_I386_ALL]];
if (!x86Header) return NO;
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
if (mh->filetype != MH_EXECUTE) return NO;
NSRange range = NSMakeRange(x86Header.offset + sizeof(struct mach_header),
sizeof(struct segment_command));
NSRange range =
NSMakeRange(x86Header.offset + sizeof(struct mach_header), sizeof(struct segment_command));
NSData *lcData = [self safeSubdataWithRange:range];
if (!lcData) return NO;
@@ -310,9 +306,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
struct load_command *lc = (struct load_command *)[lcData bytes];
if (lc->cmd == LC_SEGMENT) {
struct segment_command *segment = (struct segment_command *)lc;
if (segment->vmaddr == 0 && segment->vmsize != 0 &&
segment->initprot == 0 && segment->maxprot == 0 &&
strcmp("__PAGEZERO", segment->segname) == 0) {
if (segment->vmaddr == 0 && segment->vmsize != 0 && segment->initprot == 0 &&
segment->maxprot == 0 && strcmp("__PAGEZERO", segment->segname) == 0) {
return NO;
}
}
@@ -362,7 +357,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
while (pathComponents.count > 1) {
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if ([bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
if (!ancestor ||
if ((!ancestor && bndl.bundlePath.pathExtension.length) ||
[[self allowedAncestorExtensions] containsObject:bndl.bundlePath.pathExtension]) {
bundle = bndl;
}
@@ -376,7 +371,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (NSBundle *)bundle {
if (!self.bundleRef) {
self.bundleRef =
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
}
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
}
@@ -417,8 +412,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
}
- (NSString *)bundleName {
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
[[self.infoPlist objectForKey:@"CFBundleName"] description];
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description]
?: [[self.infoPlist objectForKey:@"CFBundleName"] description];
}
- (NSString *)bundleVersion {
@@ -467,8 +462,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0,
4096)]];
NSData *machHeader =
[self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0, 4096)]];
if (machHeader) {
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
@@ -551,24 +546,51 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
for (uint32_t i = 0; i < ncmds; ++i) {
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
if (!cmdData) return nil;
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
if (is64) {
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
if (lc->cmd == LC_SEGMENT_64 && memcmp(lc->segname, "__TEXT", 6) == 0) {
nsects = lc->nsects;
offset += sz_segment;
break;
}
offset += lc->cmdsize;
} else {
struct segment_command *lc = (struct segment_command *)[cmdData bytes];
if (lc->cmd == LC_SEGMENT && memcmp(lc->segname, "__TEXT", 6) == 0) {
nsects = lc->nsects;
offset += sz_segment;
break;
}
offset += lc->cmdsize;
}
offset += lc->cmdsize;
}
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
for (uint32_t i = 0; i < nsects; ++i) {
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
if (!sectData) return nil;
struct section_64 *sect = (struct section_64 *)[sectData bytes];
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
uint64_t sectoffset, sectsize = 0;
BOOL found = NO;
if (is64) {
struct section_64 *sect = (struct section_64 *)[sectData bytes];
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
sectoffset = sect->offset;
sectsize = sect->size;
found = YES;
}
} else {
struct section *sect = (struct section *)[sectData bytes];
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
sectoffset = sect->offset;
sectsize = sect->size;
found = YES;
}
}
if (found) {
NSData *plistData =
[self safeSubdataWithRange:NSMakeRange(mhwo.offset + sectoffset, sectsize)];
if (!plistData) return nil;
NSDictionary *plist;
plist = [NSPropertyListSerialization propertyListWithData:plistData
@@ -640,9 +662,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
}
NSURL *dbPath = [NSURL fileURLWithPathComponents:@[
fileOwnerHomeDir,
@"Library",
@"Preferences",
fileOwnerHomeDir, @"Library", @"Preferences",
@"com.apple.LaunchServices.QuarantineEventsV2"
]];
FMDatabase *db = [FMDatabase databaseWithPath:[dbPath absoluteString]];
@@ -661,7 +681,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
quarantineDict[@"LSQuarantineDataURL"] = [NSURL URLWithString:dataURLString];
quarantineDict[@"LSQuarantineOriginURL"] = [NSURL URLWithString:originURLString];
quarantineDict[@"LSQuarantineTimestamp"] =
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
self.quarantineDict = quarantineDict;
}

View File

@@ -39,9 +39,8 @@
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
XCTAssertEqualObjects(sut.path, @"/bin/ls");
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/AppleFileServer"];
XCTAssertEqualObjects(sut.path, @"/System/Library/CoreServices/AppleFileServer.app/"
@"Contents/MacOS/AppleFileServer");
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/DirectoryService"];
XCTAssertEqualObjects(sut.path, @"/usr/libexec/dspluginhelperd");
}
- (void)testSHA1 {
@@ -72,7 +71,6 @@
XCTAssertTrue(sut.isExecutable);
XCTAssertFalse(sut.isDylib);
XCTAssertFalse(sut.isFat);
XCTAssertFalse(sut.isKext);
XCTAssertFalse(sut.isScript);
}
@@ -92,9 +90,8 @@
}
- (void)testKext {
SNTFileInfo *sut =
[[SNTFileInfo alloc] initWithPath:
@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
SNTFileInfo *sut = [[SNTFileInfo alloc]
initWithPath:@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
XCTAssertTrue(sut.isMachO);
XCTAssertTrue(sut.isKext);
@@ -106,7 +103,7 @@
}
- (void)testDylibs {
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/libsqlite3.dylib"];
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/system/libsystem_platform.dylib"];
XCTAssertTrue(sut.isMachO);
XCTAssertTrue(sut.isDylib);
@@ -220,8 +217,7 @@
}
- (void)testNonBundle {
SNTFileInfo *sut =
[[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
XCTAssertNil([sut bundle]);
@@ -231,10 +227,16 @@
}
- (void)testEmbeddedInfoPlist {
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"32bitplist"
ofType:@""];
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
XCTAssertNotNil([sut infoPlist]);
XCTAssertEqualObjects([sut infoPlist][@"CFBundleShortVersionString"], @"1.0");
XCTAssertEqualObjects([sut infoPlist][@"CFBundleIdentifier"], @"com.google.i386plist");
// csreq is installed on all machines with Xcode installed. If you're running these tests,
// it should be available..
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
XCTAssertNotNil([sut infoPlist]);
}

View File

@@ -16,17 +16,18 @@
/// Common defines between kernel <-> userspace
///
#include <sys/param.h>
#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 likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// List of methods supported by the driver.
@@ -81,15 +82,16 @@ typedef enum {
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 || \
#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
@@ -98,11 +100,11 @@ typedef struct santa_vnode_id_t {
uint64_t fileid;
#ifdef __cplusplus
bool operator==(const santa_vnode_id_t& rhs) const {
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.
// 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);
}
@@ -116,6 +118,7 @@ typedef struct {
uid_t uid;
gid_t gid;
pid_t pid;
int pidversion;
pid_t ppid;
char path[MAXPATHLEN];
char newpath[MAXPATHLEN];
@@ -125,10 +128,12 @@ typedef struct {
// 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.
// 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.
// For messages that originate from EndpointSecurity, this points to an
// NSArray of the arguments.
void *args_array;
} santa_message_t;

View File

@@ -56,7 +56,7 @@ typedef enum : NSUInteger {
/// @param ... the arguments to format.
///
void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
__attribute__((format(__NSString__, 3, 4)));
__attribute__((format(__NSString__, 3, 4)));
/// Simple logging macros
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
@@ -65,7 +65,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
#ifdef __cplusplus
} // extern C
} // extern C
#endif
#endif // KERNEL

View File

@@ -14,6 +14,8 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTConfigurator.h"
#import <asl.h>
#import <pthread.h>
@@ -39,8 +41,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
dispatch_once(&pred, ^{
binaryName = [[NSProcessInfo processInfo] processName];
// If debug logging is enabled, the process must be restarted.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
if ([SNTConfigurator configurator].enableDebugLogging) {
logLevel = LOG_LEVEL_DEBUG;
}
@@ -83,11 +84,14 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
break;
case LOG_LEVEL_INFO:
levelName = "I";
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
break;
case LOG_LEVEL_DEBUG:
levelName = "D";
syslogLevel = ASL_LEVEL_DEBUG;
// Log debug messages at the same ASL level as INFO.
// While it would make sense to use DEBUG, watching debug-level logs
// in Console means enabling all debug logs, which is absurdly noisy.
syslogLevel = ASL_LEVEL_NOTICE;
break;
}

View File

@@ -0,0 +1,197 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import "SNTCommonEnums.h"
/**
* Provides an abstraction for various metric systems that will be exported to
* monitoring systems via the MetricService. This is used to store internal
* counters and metrics that can be exported to an external monitoring system.
*
* `SNTMetricSet` for storing and creating metrics and counters. This is
* the externally visible interface
* class.
*
* Metric classes:
* * `SNTMetric` to store metric values broken down by "field" dimensions.
* * subclasses of `SNTMetric` with suitable setters:
* * `SNTMetricCounter`
* * `SNTMetricGaugeInt64`
* * `SNTMetricGaugeDouble`
* * `SNTMetricString`
* * `SNTMetricBool`
*/
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, SNTMetricType) {
SNTMetricTypeUnknown = 0,
SNTMetricTypeConstantBool = 1,
SNTMetricTypeConstantString = 2,
SNTMetricTypeConstantInt64 = 3,
SNTMetricTypeConstantDouble = 4,
SNTMetricTypeGaugeBool = 5,
SNTMetricTypeGaugeString = 6,
SNTMetricTypeGaugeInt64 = 7,
SNTMetricTypeGaugeDouble = 8,
SNTMetricTypeCounter = 9,
};
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType);
@interface SNTMetric : NSObject
- (NSDictionary *)export;
@end
@interface SNTMetricCounter : SNTMetric
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues;
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues;
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues;
@end
@interface SNTMetricInt64Gauge : SNTMetric
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues;
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
@end
@interface SNTMetricDoubleGauge : SNTMetric
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues;
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
@end
@interface SNTMetricStringGauge : SNTMetric
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues;
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues;
@end
@interface SNTMetricBooleanGauge : SNTMetric
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues;
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues;
@end
/**
* A registry of metrics with associated fields.
*/
@interface SNTMetricSet : NSObject
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username;
/* Returns a counter with the given name, field names and help
* text, registered with the MetricSet.
*
* @param name The counter name, for example @"/proc/cpu".
* @param fieldNames The counter's field names, for example @[@"result"].
* @param helpText The counter's help description.
* @return A counter with the given specification registered with this root.
* The returned counter might have been created earlier with the same
* specification.
* @throw NSInternalInconsistencyException When trying to register a second
* counter with the same name but a different schema as an existing one
*/
- (SNTMetricCounter *)counterWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)text;
/**
* Returns a shared global instance with default root labels and metrics registered.
*/
+ (instancetype)sharedInstance;
/**
* Add a root label to the MetricSet.
*/
- (void)addRootLabel:(NSString *)label value:(NSString *)value;
/**
* Remove a root label from the MetricSet.
*/
- (void)removeRootLabel:(NSString *)labelName;
/**
* Returns a int64 gauge metric with the given Streamz name and help text,
* registered with this MetricSet.
*
* @param name The metric name, for example @"/memory/free".
* @param fieldNames The metric's field names, for example @[@"type"].
* @param helpText The metric's help description.
*/
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText;
/**
* Returns a double gauge metric with the given name and help text,
* registered with this root.
*
* @param name The metric name, for example @"/memory/free".
* @param fieldNames The metric's field names, for example @[@"type"].
* @param helpText The metric's help description.
*/
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText;
/**
* Returns a string gauge metric with the given name and help text,
* registered with this metric set.
*
* @param name The metric name, for example @"/santa/mode".
* @param fieldNames The metric's field names, for example @[@"type"].
* @param helpText The metric's help description.
*/
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText;
/**
* Returns a boolean gauge metric with the given name and help text,
* registered with this metric set.
*
* @param name The metric name, for example @"/memory/free".
* @param fieldNames The metric's field names, for example @[@"type"].
* @param helpText The metric's help description.
*/
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText;
/** Creates a constant metric with a string value and no fields. */
- (void)addConstantStringWithName:(NSString *)name
helpText:(NSString *)helpText
value:(NSString *)value;
/** Creates a constant metric with an integer value and no fields. */
- (void)addConstantIntegerWithName:(NSString *)name
helpText:(NSString *)helpText
value:(long long)value;
/** Creates a constant metric with an integer value and no fields. */
- (void)addConstantBooleanWithName:(NSString *)name helpText:(NSString *)helpText value:(BOOL)value;
/** Register a callback to get executed just before each export. */
- (void)registerCallback:(void (^)(void))callback;
/** Export creates an NSDictionary of the state of the metrics */
- (NSDictionary *)export;
@end
// Returns a human readble string from an SNTMetricFormat type
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format);
/** Normalizes dates in an exported dictionary to be ISO8601 timestamp strings in
* UTC time.
*/
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,677 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTMetricSet.h"
#import "SNTCommonEnums.h"
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
NSString *typeStr;
switch (metricType) {
case SNTMetricTypeConstantBool: typeStr = @"SNTMetricTypeConstantBool"; break;
case SNTMetricTypeConstantString: typeStr = @"SNTMetricTypeConstantString"; break;
case SNTMetricTypeConstantInt64: typeStr = @"SNTMetricTypeConstantInt64"; break;
case SNTMetricTypeConstantDouble: typeStr = @"SNTMetricTypeConstantDouble"; break;
case SNTMetricTypeGaugeBool: typeStr = @"SNTMetricTypeGaugeBool"; break;
case SNTMetricTypeGaugeString: typeStr = @"SNTMetricTypeGaugeString"; break;
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
default: typeStr = @"SNTMetricTypeUnknown"; break;
}
return [NSString stringWithFormat:@"%@ %ld", typeStr, metricType];
}
/**
* SNTMetricValue encapsulates the value of a metric along with the creation
* and update timestamps. It is thread-safe and has a separate field for each
* metric type.
*
* It is intended to only be used by SNTMetrics;
*/
@interface SNTMetricValue : NSObject
/** Increment the counter by the step value, updating timestamps appropriately. */
- (void)addInt64:(long long)step;
/** Set the Int64 value. */
- (void)setInt64:(long long)value;
/** Set the double value. */
- (void)setDouble:(double)value;
/** Set the string value. */
- (void)setString:(NSString *)value;
/** Set the BOOL string value. */
- (void)setBool:(BOOL)value;
/**
* Clears the last update timestamp.
*
* This makes the metric value always emit the current timestamp as last update timestamp.
*/
- (void)clearLastUpdateTimestamp;
/** Getters */
- (long long)getInt64Value;
- (double)getDoubleValue;
- (NSString *)getStringValue;
@end
@implementation SNTMetricValue {
/** The int64 value for the SNTMetricValue, if set. */
long long _int64Value;
/** The double value for the SNTMetricValue, if set. */
double _doubleValue;
/** The string value for the SNTMetricValue, if set. */
NSString *_stringValue;
/** The boolean value for the SNTMetricValue, if set. */
BOOL _boolValue;
/** The first time this cell got created in the current process. */
NSDate *_creationTime;
/** The last time that the counter value was changed. */
NSDate *_lastUpdate;
}
- (instancetype)init {
self = [super init];
if (self) {
_creationTime = [NSDate date];
_lastUpdate = _creationTime;
}
return self;
}
- (void)addInt64:(long long)step {
@synchronized(self) {
_int64Value += step;
_lastUpdate = [NSDate date];
}
}
- (void)setInt64:(long long)value {
@synchronized(self) {
_int64Value = value;
_lastUpdate = [NSDate date];
}
}
- (long long)getInt64Value {
@synchronized(self) {
return _int64Value;
}
}
- (void)setDouble:(double)value {
@synchronized(self) {
_doubleValue = value;
_lastUpdate = [NSDate date];
}
}
- (double)getDoubleValue {
@synchronized(self) {
return _doubleValue;
}
}
- (void)setString:(NSString *)value {
@synchronized(self) {
_stringValue = [value copy];
_lastUpdate = [NSDate date];
}
}
- (NSString *)getStringValue {
@synchronized(self) {
return [_stringValue copy];
}
}
- (void)setBool:(BOOL)value {
@synchronized(self) {
_boolValue = value;
_lastUpdate = [NSDate date];
}
}
- (BOOL)getBoolValue {
@synchronized(self) {
return _boolValue;
}
}
- (void)clearLastUpdateTimestamp {
@synchronized(self) {
_lastUpdate = nil;
}
}
- (NSDate *)getLastUpdatedTimestamp {
NSDate *updated = nil;
@synchronized(self) {
updated = [_lastUpdate copy];
}
return updated;
}
- (NSDate *)getCreatedTimestamp {
NSDate *created = nil;
@synchronized(self) {
created = [_creationTime copy];
}
return created;
}
@end
@implementation SNTMetric {
@private
/** Fully qualified metric name e.g. /ops/security/santa. */
NSString *_name;
/** A help text for the metric to be exported to be exported. **/
NSString *_help;
/** Sorted list of the fieldNames **/
NSArray<NSString *> *_fieldNames;
/** Mapping of field values to actual metric values (e.g. metric /proc/cpu_usage @"mode"=@"user"
* -> 0.89 */
NSMutableDictionary<NSArray<NSString *> *, SNTMetricValue *> *_metricsForFieldValues;
/** the type of metric this is e.g. counter, gauge etc. **/
SNTMetricType _type;
}
- (instancetype)initWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)help
type:(SNTMetricType)type {
self = [super init];
if (self) {
_name = [name copy];
_help = [help copy];
_fieldNames = [fieldNames copy];
_metricsForFieldValues = [[NSMutableDictionary alloc] init];
_type = type;
}
return self;
}
- (NSString *)name {
return _name;
}
- (BOOL)hasSameSchemaAsMetric:(SNTMetric *)other {
if (![other isKindOfClass:[self class]]) {
return NO;
}
return [_name isEqualToString:other->_name] && [_help isEqualToString:other->_help] &&
[_fieldNames isEqualTo:other->_fieldNames] && _type == other->_type;
}
/** Retrieves the SNTMetricValue for a given field value.
Creates a new SNTMetricValue if none is present. */
- (SNTMetricValue *)metricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
NSParameterAssert(fieldValues.count == _fieldNames.count);
SNTMetricValue *metricValue = nil;
@synchronized(self) {
metricValue = _metricsForFieldValues[fieldValues];
if (!metricValue) {
// Deep copy to prevent mutations to the keys we store in the dictionary.
fieldValues = [fieldValues copy];
metricValue = [[SNTMetricValue alloc] init];
_metricsForFieldValues[fieldValues] = metricValue;
}
}
return metricValue;
}
- (NSDictionary *)encodeMetricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = _metricsForFieldValues[fieldValues];
NSMutableDictionary *fieldDict = [[NSMutableDictionary alloc] init];
fieldDict[@"created"] = [metricValue getCreatedTimestamp];
fieldDict[@"last_updated"] = [metricValue getLastUpdatedTimestamp];
fieldDict[@"value"] = [fieldValues componentsJoinedByString:@","];
switch (_type) {
case SNTMetricTypeConstantBool:
case SNTMetricTypeGaugeBool:
fieldDict[@"data"] = [NSNumber numberWithBool:[metricValue getBoolValue]];
break;
case SNTMetricTypeConstantInt64:
case SNTMetricTypeCounter:
case SNTMetricTypeGaugeInt64:
fieldDict[@"data"] = [NSNumber numberWithLongLong:[metricValue getInt64Value]];
break;
case SNTMetricTypeConstantDouble:
case SNTMetricTypeGaugeDouble:
fieldDict[@"data"] = [NSNumber numberWithDouble:[metricValue getDoubleValue]];
break;
case SNTMetricTypeConstantString:
case SNTMetricTypeGaugeString: fieldDict[@"data"] = [metricValue getStringValue]; break;
default: break;
}
return fieldDict;
}
- (NSDictionary *)export {
NSMutableDictionary *metricDict = [NSMutableDictionary dictionaryWithCapacity:_fieldNames.count];
metricDict[@"type"] = [NSNumber numberWithInt:(int)_type];
metricDict[@"fields"] = [[NSMutableDictionary alloc] init];
metricDict[@"description"] = [_help copy];
if (_fieldNames.count == 0) {
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
} else {
for (NSString *fieldName in _fieldNames) {
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
}
metricDict[@"fields"][fieldName] = fieldVals;
}
}
return metricDict;
}
@end
@implementation SNTMetricCounter
- (instancetype)initWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
return [super initWithName:name
fieldNames:fieldNames
helpText:helpText
type:SNTMetricTypeCounter];
}
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
if (!metricValue) {
return;
}
[metricValue addInt64:step];
}
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues {
[self incrementBy:1 forFieldValues:fieldValues];
}
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
if (!metricValue) {
return -1;
}
return [metricValue getInt64Value];
}
@end
@implementation SNTMetricInt64Gauge
- (instancetype)initWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
return [super initWithName:name
fieldNames:fieldNames
helpText:helpText
type:SNTMetricTypeGaugeInt64];
}
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
[metricValue setInt64:value];
}
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
if (!metricValue) {
return -1;
}
return [metricValue getInt64Value];
}
@end
@implementation SNTMetricDoubleGauge
- (instancetype)initWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)text {
return [super initWithName:name
fieldNames:fieldNames
helpText:text
type:SNTMetricTypeGaugeDouble];
}
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
[metricValue setDouble:value];
}
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
if (!metricValue) {
return -1;
}
return [metricValue getDoubleValue];
}
@end
@implementation SNTMetricStringGauge
- (instancetype)initWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)text {
return [super initWithName:name
fieldNames:fieldNames
helpText:text
type:SNTMetricTypeGaugeString];
}
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
[metricValue setString:value];
}
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
if (!metricValue) {
return nil;
}
return [metricValue getStringValue];
}
@end
@implementation SNTMetricBooleanGauge
- (instancetype)initWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
return [super initWithName:name
fieldNames:fieldNames
helpText:helpText
type:SNTMetricTypeGaugeBool];
}
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
[metricValue setBool:value];
}
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues {
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
if (!metricValue) {
return false;
}
return [metricValue getBoolValue];
}
@end
/**
* SNTMetricSet is the top level container for all metrics and metrics value
* its is abstracted from specific implementations but is close to Google's
* Monarch and Prometheus formats.
*/
@implementation SNTMetricSet {
@private
/** Labels that are used to identify the entity to that all metrics apply to. */
NSMutableDictionary<NSString *, NSString *> *_rootLabels;
/** Registered metrics keyed by name */
NSMutableDictionary<NSString *, SNTMetric *> *_metrics;
/** Callbacks to update metric values before exporting metrics */
NSMutableArray<void (^)(void)> *_callbacks;
}
+ (instancetype)sharedInstance {
static SNTMetricSet *sharedMetrics;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMetrics = [[SNTMetricSet alloc] init];
});
return sharedMetrics;
}
- (instancetype)init {
self = [super init];
if (self) {
_rootLabels = [[NSMutableDictionary alloc] init];
_metrics = [[NSMutableDictionary alloc] init];
_callbacks = [[NSMutableArray alloc] init];
}
return self;
}
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username {
self = [super init];
if (self) {
_rootLabels = [[NSMutableDictionary alloc] init];
_metrics = [[NSMutableDictionary alloc] init];
_callbacks = [[NSMutableArray alloc] init];
_rootLabels[@"hostname"] = [hostname copy];
_rootLabels[@"username"] = [username copy];
}
return self;
}
- (void)addRootLabel:(NSString *)label value:(NSString *)value {
@synchronized(self) {
_rootLabels[label] = value;
}
}
- (void)removeRootLabel:(NSString *)label {
@synchronized(self) {
[_rootLabels removeObjectForKey:label];
}
}
- (SNTMetric *)registerMetric:(nonnull SNTMetric *)metric {
@synchronized(self) {
SNTMetric *oldMetric = _metrics[[metric name]];
if ([oldMetric hasSameSchemaAsMetric:metric]) {
return oldMetric;
}
NSAssert(!oldMetric, @"metric registered twice: %@", metric.name);
_metrics[metric.name] = metric;
}
return metric;
}
- (void)registerCallback:(void (^)(void))callback {
@synchronized(self) {
[_callbacks addObject:callback];
}
}
- (SNTMetricCounter *)counterWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
SNTMetricCounter *c = [[SNTMetricCounter alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:c];
return c;
}
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
SNTMetricInt64Gauge *g = [[SNTMetricInt64Gauge alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:g];
return g;
}
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
SNTMetricDoubleGauge *g = [[SNTMetricDoubleGauge alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:g];
return g;
}
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
SNTMetricStringGauge *s = [[SNTMetricStringGauge alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:s];
return s;
}
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)helpText {
SNTMetricBooleanGauge *b = [[SNTMetricBooleanGauge alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:b];
return b;
}
- (void)addConstantStringWithName:(NSString *)name
helpText:(NSString *)helpText
value:(NSString *)value {
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
fieldNames:@[]
helpText:helpText
type:SNTMetricTypeConstantString];
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
[metricValue setString:value];
[self registerMetric:metric];
}
- (void)addConstantIntegerWithName:(NSString *)name
helpText:(NSString *)helpText
value:(long long)value {
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
fieldNames:@[]
helpText:helpText
type:SNTMetricTypeConstantInt64];
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
[metricValue setInt64:value];
[self registerMetric:metric];
}
- (void)addConstantBooleanWithName:(NSString *)name
helpText:(NSString *)helpText
value:(BOOL)value {
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
fieldNames:@[]
helpText:helpText
type:SNTMetricTypeConstantBool];
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
[metricValue setBool:value];
[self registerMetric:metric];
}
/** Export current state of the SNTMetricSet as an NSDictionary. */
- (NSDictionary *)export {
NSDictionary *exported = nil;
// Invoke callbacks to ensure metrics are up to date.
for (void (^cb)(void) in _callbacks) {
cb();
}
@synchronized(self) {
NSMutableDictionary *exportDict = [[NSMutableDictionary alloc] init];
exportDict[@"root_labels"] = [_rootLabels copy];
exportDict[@"metrics"] = [[NSMutableDictionary alloc] init];
// TODO(markowsky) Sort the metrics so we always get the same output.
for (NSString *metricName in _metrics) {
exportDict[@"metrics"][metricName] = [_metrics[metricName] export];
}
exported = [NSDictionary dictionaryWithDictionary:exportDict];
}
return exported;
}
// Returns a human readble string from an SNTMetricFormat type
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format) {
switch (format) {
case SNTMetricFormatTypeRawJSON: return @"rawjson";
case SNTMetricFormatTypeMonarchJSON: return @"monarchjson";
default: return @"Unknown Metric Format";
}
}
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics) {
NSMutableDictionary *mutableMetrics = [metrics mutableCopy];
id formatter;
if (@available(macOS 10.13, *)) {
NSISO8601DateFormatter *isoFormatter = [[NSISO8601DateFormatter alloc] init];
isoFormatter.formatOptions =
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
formatter = isoFormatter;
} else {
NSDateFormatter *localFormatter = [[NSDateFormatter alloc] init];
[localFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[localFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
formatter = localFormatter;
}
for (NSString *metricName in mutableMetrics[@"metrics"]) {
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];
for (NSString *field in metric[@"fields"]) {
NSMutableArray<NSMutableDictionary *> *values = metric[@"fields"][field];
[values enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
values[index][@"created"] = [formatter stringFromDate:values[index][@"created"]];
values[index][@"last_updated"] = [formatter stringFromDate:values[index][@"last_updated"]];
}];
}
}
return mutableMetrics;
}
@end

View File

@@ -0,0 +1,607 @@
#import <XCTest/XCTest.h>
#import "Source/common/SNTMetricSet.h"
@interface SNTMetricCounterTest : XCTestCase
@end
@interface SNTMetricGaugeInt64Test : XCTestCase
@end
@interface SNTMetricDoubleGaugeTest : XCTestCase
@end
@interface SNTMetricBooleanGaugeTest : XCTestCase
@end
@interface SNTMetricStringGaugeTest : XCTestCase
@end
@interface SNTMetricSetTest : XCTestCase
@end
@interface SNTMetricSetHelperFunctionsTest : XCTestCase
@end
// Stub out NSDate's date method
@implementation NSDate (custom)
+ (instancetype)date {
NSDateFormatter *formatter = NSDateFormatter.new;
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
return [formatter dateFromString:@"2021-08-05 13:00:10+0000"];
}
@end
@implementation SNTMetricCounterTest
- (void)testSimpleCounter {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricCounter *c =
[metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"rule_type" ]
helpText:@"Count of exec events broken out by rule type."];
XCTAssertNotNil(c, @"Expected returned SNTMetricCounter to not be nil");
[c incrementForFieldValues:@[ @"certificate" ]];
XCTAssertEqual(1, [c getCountForFieldValues:@[ @"certificate" ]],
@"Counter not incremendted by 1");
[c incrementBy:3 forFieldValues:@[ @"certificate" ]];
XCTAssertEqual(4, [c getCountForFieldValues:@[ @"certificate" ]],
@"Counter not incremendted by 3");
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricCounter *c =
[metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"rule_type" ]
helpText:@"Count of exec events broken out by rule type."];
XCTAssertNotNil(c);
[c incrementForFieldValues:@[ @"certificate" ]];
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
@"description" : @"Count of exec events broken out by rule type.",
@"fields" : @{
@"rule_type" : @[ @{
@"value" : @"certificate",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:1]
} ]
}
};
XCTAssertEqualObjects([c export], expected);
}
@end
@implementation SNTMetricBooleanGaugeTest
- (void)testSimpleGauge {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
fieldNames:@[]
helpText:@"Is the daemon connected."];
XCTAssertNotNil(b);
[b set:true forFieldValues:@[]];
XCTAssertTrue([b getBoolValueForFieldValues:@[]]);
[b set:false forFieldValues:@[]];
XCTAssertFalse([b getBoolValueForFieldValues:@[]]);
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
fieldNames:@[]
helpText:@"Is the daemon connected."];
XCTAssertNotNil(b);
[b set:true forFieldValues:@[]];
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeBool],
@"description" : @"Is the daemon connected.",
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithBool:true]
} ]
}
};
NSDictionary *output = [b export];
XCTAssertEqualObjects(output, expected);
}
@end
@implementation SNTMetricGaugeInt64Test
- (void)testSimpleGauge {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricInt64Gauge *g =
[metricSet int64GaugeWithName:@"/santa/rules"
fieldNames:@[ @"rule_type" ]
helpText:@"Count of rules broken out by rule type."];
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
// set from zero
[g set:250 forFieldValues:@[ @"binary" ]];
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
// Increase the gauge
[g set:500 forFieldValues:@[ @"binary" ]];
XCTAssertEqual(500, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
// Decrease after increase
[g set:100 forFieldValues:@[ @"binary" ]];
XCTAssertEqual(100, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
// Increase after decrease
[g set:750 forFieldValues:@[ @"binary" ]];
XCTAssertEqual(750, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
// TODO: export the tree to JSON and confirm the structure is correct.
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricInt64Gauge *g =
[metricSet int64GaugeWithName:@"/santa/rules"
fieldNames:@[ @"rule_type" ]
helpText:@"Count of rules broken out by rule type."];
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
// set from zero
[g set:250 forFieldValues:@[ @"binary" ]];
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"description" : @"Count of rules broken out by rule type.",
@"fields" : @{
@"rule_type" : @[ @{
@"value" : @"binary",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:250]
} ]
}
};
XCTAssertEqualObjects([g export], expected);
}
@end
@implementation SNTMetricDoubleGaugeTest
- (void)testSimpleGauge {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
fieldNames:@[ @"mode" ]
helpText:@"CPU time consumed by this process."];
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
// set from zero
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
XCTAssertEqual(0.45, [g getGaugeValueForFieldValues:@[ @"user" ]]);
// Increase the gauge
[g set:(double)0.90 forFieldValues:@[ @"user" ]];
XCTAssertEqual(0.90, [g getGaugeValueForFieldValues:@[ @"user" ]]);
// Decrease after increase
[g set:0.71 forFieldValues:@[ @"user" ]];
XCTAssertEqual(0.71, [g getGaugeValueForFieldValues:@[ @"user" ]]);
// Increase after decrease
[g set:0.75 forFieldValues:@[ @"user" ]];
XCTAssertEqual(0.75, [g getGaugeValueForFieldValues:@[ @"user" ]]);
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
fieldNames:@[ @"mode" ]
helpText:@"CPU time consumed by this process."];
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
// set from zero
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
[g set:(double)0.90 forFieldValues:@[ @"system" ]];
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeDouble],
@"description" : @"CPU time consumed by this process.",
@"fields" : @{
@"mode" : @[
@{
@"value" : @"user",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithDouble:0.45]
},
@{
@"value" : @"system",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithDouble:0.90]
}
]
}
};
XCTAssertEqualObjects([g export], expected);
}
@end
@implementation SNTMetricStringGaugeTest
- (void)testSimpleGauge {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
fieldNames:@[]
helpText:@"String description of the mode."];
XCTAssertNotNil(s);
[s set:@"testValue" forFieldValues:@[]];
XCTAssertEqualObjects([s getStringValueForFieldValues:@[]], @"testValue");
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
fieldNames:@[]
helpText:@"String description of the mode."];
XCTAssertNotNil(s);
[s set:@"testValue" forFieldValues:@[]];
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeString],
@"description" : @"String description of the mode.",
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : @"testValue"
} ]
}
};
XCTAssertEqualObjects([s export], expected);
}
@end
@implementation SNTMetricSetTest
- (void)testRootLabels {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
[metricSet addRootLabel:@"hostname" value:@"localhost"];
NSDictionary *expected = @{@"root_labels" : @{@"hostname" : @"localhost"}, @"metrics" : @{}};
XCTAssertEqualObjects(expected, [metricSet export]);
// ensure that adding a rootLabel with the same name overwrites.
expected = @{@"root_labels" : @{@"hostname" : @"localhost2"}, @"metrics" : @{}};
[metricSet addRootLabel:@"hostname" value:@"localhost2"];
XCTAssertEqualObjects(expected, [metricSet export],
@"failed to overwrite rootLabel with second call to addRootLabel");
// ensure that removing a rootLabelWorks
expected = @{@"root_labels" : @{}, @"metrics" : @{}};
[metricSet removeRootLabel:@"hostname"];
}
- (void)testDoubleRegisteringIncompatibleMetricsFails {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricCounter *c = [metricSet counterWithName:@"/foo/bar"
fieldNames:@[ @"field" ]
helpText:@"lorem ipsum"];
XCTAssertNotNil(c);
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
fieldNames:@[ @"incompatible" ]
helpText:@"A little help text"],
@"Should raise error for incompatible field names");
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
fieldNames:@[ @"result" ]
helpText:@"INCOMPATIBLE"],
@"Should raise error for incompatible help text");
}
- (void)testRegisterCallback {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
// Register a callback metric which increments by one before export
SNTMetricInt64Gauge *gauge = [metricSet int64GaugeWithName:@"/foo/bar"
fieldNames:@[]
helpText:@"Number of callbacks done"];
__block int count = 0;
[metricSet registerCallback:^(void) {
count++;
[gauge set:count forFieldValues:@[]];
}];
// ensure the callback is called.
[metricSet export];
XCTAssertEqual([gauge getGaugeValueForFieldValues:@[]], 1);
}
- (void)testAddConstantBool {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
[metricSet addConstantBooleanWithName:@"/tautology"
helpText:@"The first rule of tautology club is the first rule"
value:YES];
NSDictionary *expected = @{
@"/tautology" : @{
@"description" : @"The first rule of tautology club is the first rule",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithBool:true]
} ]
}
}
};
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
}
- (void)testAddConstantString {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
[metricSet addConstantStringWithName:@"/build/label"
helpText:@"Build label for the binary"
value:@"20210806.0.1"];
NSDictionary *expected = @{
@"/build/label" : @{
@"description" : @"Build label for the binary",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : @"20210806.0.1"
} ]
}
}
};
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
}
- (void)testAddConstantInt {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
[metricSet addConstantIntegerWithName:@"/deep/thought/answer"
helpText:@"Life, the universe, and everything"
value:42];
NSDictionary *expected = @{
@"/deep/thought/answer" : @{
@"description" : @"Life, the universe, and everything",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithLongLong:42]
} ]
}
}
};
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
username:@"testUser"];
// Add constants
[metricSet addConstantStringWithName:@"/build/label"
helpText:@"Software version running."
value:@"20210809.0.1"];
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
helpText:@"Is santad using the endpoint security framework."
value:TRUE];
[metricSet
addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this santad instance, in microseconds since epoch"
value:(long long)(0x12345668910)];
// Add Metrics
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"rule_type" ]
helpText:@"Count of events on the host"];
[c incrementForFieldValues:@[ @"binary" ]];
[c incrementBy:2 forFieldValues:@[ @"certificate" ]];
SNTMetricInt64Gauge *g = [metricSet int64GaugeWithName:@"/santa/rules"
fieldNames:@[ @"rule_type" ]
helpText:@"Number of rules."];
[g set:1 forFieldValues:@[ @"binary" ]];
[g set:3 forFieldValues:@[ @"certificate" ]];
// Add Metrics with callback
SNTMetricInt64Gauge *virtualMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
fieldNames:@[]
helpText:@"The virtual memory size of this process."];
SNTMetricInt64Gauge *residentMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
fieldNames:@[]
helpText:@"The resident set size of this process."];
[metricSet registerCallback:^(void) {
[virtualMemoryGauge set:987654321 forFieldValues:@[]];
[residentMemoryGauge set:123456789 forFieldValues:@[]];
}];
NSDictionary *expected = @{
@"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"},
@"metrics" : @{
@"/build/label" : @{
@"description" : @"Software version running.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : @"20210809.0.1"
} ]
}
},
@"/santa/events" : @{
@"description" : @"Count of events on the host",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
@"fields" : @{
@"rule_type" : @[
@{
@"value" : @"binary",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:1],
},
@{
@"value" : @"certificate",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:2],
},
],
},
},
@"/santa/rules" : @{
@"description" : @"Number of rules.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"rule_type" : @[
@{
@"value" : @"binary",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:1],
},
@{
@"value" : @"certificate",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:3],
}
]
},
},
@"/santa/using_endpoint_security_framework" : @{
@"description" : @"Is santad using the endpoint security framework.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithBool:YES]
} ]
}
},
@"/proc/birth_timestamp" : @{
@"description" : @"Start time of this santad instance, in microseconds since epoch",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithLong:1250999830800]
} ]
},
},
@"/proc/memory/virtual_size" : @{
@"description" : @"The virtual memory size of this process.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:987654321]
} ]
}
},
@"/proc/memory/resident_size" : @{
@"description" : @"The resident set size of this process.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@"created" : [NSDate date],
@"last_updated" : [NSDate date],
@"data" : [NSNumber numberWithInt:123456789]
} ]
},
},
}
};
XCTAssertEqualObjects([metricSet export], expected);
}
@end
@implementation SNTMetricSetHelperFunctionsTest
- (void)testMakeMetricString {
NSArray<NSDictionary *> *tests = @[
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeUnknown],
@"expected" : @"SNTMetricTypeUnknown 0"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
@"expected" : @"SNTMetricTypeConstantBool 1"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
@"expected" : @"SNTMetricTypeConstantString 2"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
@"expected" : @"SNTMetricTypeConstantInt64 3"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
@"expected" : @"SNTMetricTypeConstantDouble 4"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
@"expected" : @"SNTMetricTypeGaugeBool 5"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
@"expected" : @"SNTMetricTypeGaugeString 6"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
@"expected" : @"SNTMetricTypeGaugeInt64 7"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
@"expected" : @"SNTMetricTypeGaugeDouble 8"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
@"expected" : @"SNTMetricTypeCounter 9"
}
];
for (NSDictionary *test in tests) {
NSString *output = SNTMetricMakeStringFromMetricType([test[@"input"] integerValue]);
XCTAssertEqualObjects(test[@"expected"], output, @"expected %@ got %@", test[@"expected"],
output);
}
}
@end

View File

@@ -16,27 +16,37 @@
#ifdef KERNEL
#include <libkern/locks.h>
#include "Source/common/SNTLogging.h"
#else
#include <mutex>
#include <string.h>
#define LOGD(format, ...) // NOP
#define LOGE(format, ...) // NOP
#include <mutex>
#define LOGD(format, ...) // NOP
#define LOGE(format, ...) // NOP
#define lck_rw_lock_shared(l) pthread_rwlock_rdlock(&l)
#define lck_rw_unlock_shared(l) pthread_rwlock_unlock(&l)
#define lck_rw_lock_exclusive(l) pthread_rwlock_wrlock(&l)
#define lck_rw_unlock_exclusive(l) pthread_rwlock_unlock(&l)
#define lck_rw_lock_shared_to_exclusive(l) ({ pthread_rwlock_unlock(&l); false; })
#define lck_rw_lock_exclusive_to_shared(l) ({ pthread_rwlock_unlock(&l); pthread_rwlock_rdlock(&l); })
#define lck_rw_lock_shared_to_exclusive(l) \
({ \
pthread_rwlock_unlock(&l); \
false; \
})
#define lck_rw_lock_exclusive_to_shared(l) \
({ \
pthread_rwlock_unlock(&l); \
pthread_rwlock_rdlock(&l); \
})
#define lck_mtx_lock(l) l->lock()
#define lck_mtx_unlock(l) l->unlock()
#endif // KERNEL
#endif // KERNEL
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
root_ = new SantaPrefixNode();
@@ -45,7 +55,8 @@ SNTPrefixTree::SNTPrefixTree(uint32_t 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_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_);
@@ -57,9 +68,9 @@ SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
}
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could overwrite whole
// branches of another. HasPrefix is still free to read the tree, until AddPrefix needs to
// modify it.
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could
// overwrite whole branches of another. HasPrefix is still free to read the
// tree, until AddPrefix needs to modify it.
lck_mtx_lock(spt_add_lock_);
// Don't allow an empty prefix.
@@ -74,13 +85,13 @@ IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
lck_rw_lock_shared(spt_lock_);
SantaPrefixNode *node = root_;
for (int i = 0; i < len; ++i) {
for (size_t i = 0; i < len; ++i) {
// If there is a node in the path that is considered a prefix, stop adding.
// For our purposes we only care about the shortest path that matches.
if (node->isPrefix) break;
// Only process a byte at a time.
uint8_t value = prefix[i];
uint8_t value = (uint8_t)prefix[i];
// Create the child if it does not exist.
if (!node->children[value]) {
@@ -103,7 +114,7 @@ IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
// Create the rest of the prefix.
while (i < len) {
value = prefix[i++];
value = (uint8_t)prefix[i++];
SantaPrefixNode *new_node = new SantaPrefixNode();
node->children[value] = new_node;
@@ -159,7 +170,8 @@ bool SNTPrefixTree::HasPrefix(const char *string) {
SantaPrefixNode *node = root_;
// A well formed tree will always break this loop. Even if string doesn't terminate.
// A well formed tree will always break this loop. Even if string doesn't
// terminate.
const char *p = string;
while (*p) {
// Only process a byte at a time.
@@ -193,8 +205,8 @@ void SNTPrefixTree::Reset() {
void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
if (!target) return;
// For deep trees, a recursive approach will generate too many stack frames. Make a "stack"
// and walk the tree.
// For deep trees, a recursive approach will generate too many stack frames.
// Make a "stack" and walk the tree.
auto stack = new SantaPrefixNode *[node_count_ + 1];
if (!stack) {
LOGE("Unable to prune tree!");
@@ -206,7 +218,8 @@ void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
// Seed the "stack" with a starting node.
stack[count++] = target;
// Start at the target node and walk the tree to find and delete all the sub-nodes.
// Start at the target node and walk the tree to find and delete all the
// sub-nodes.
while (count) {
auto node = stack[--count];

View File

@@ -22,10 +22,11 @@
#include <libkern/locks.h>
#else
// Support for unit testing.
#include <mutex>
#include <pthread.h>
#include <stdint.h>
#endif // KERNEL
#include <mutex>
#endif // KERNEL
///
/// SantaPrefixTree is a simple prefix tree implementation.
@@ -54,15 +55,17 @@ class SNTPrefixTree {
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
///
/// The path for "/🤘" would look like this:
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4] -> children[0x98]
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4]
/// -> children[0x98]
///
/// The path for "/dev" is:
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
///
/// Lookups of children are O(1).
///
/// Having the nodes represented by a smaller width, such as a nibble (1/2 byte), would
/// drastically decrease the memory footprint but would double required dereferences.
/// Having the nodes represented by a smaller width, such as a nibble (1/2
/// byte), would drastically decrease the memory footprint but would double
/// required dereferences.
///
/// TODO(bur): Potentially convert this into a full on radix tree.
///
@@ -74,8 +77,8 @@ class SNTPrefixTree {
// PruneNode will remove the passed in node from the tree.
// The passed in node and all subnodes will be deleted.
// It is the caller's responsibility to reset the pointer to this node (held by the parent).
// If the tree is in use grab the exclusive lock.
// It is the caller's responsibility to reset the pointer to this node (held
// by the parent). If the tree is in use grab the exclusive lock.
void PruneNode(SantaPrefixNode *);
SantaPrefixNode *root_;
@@ -91,13 +94,10 @@ class SNTPrefixTree {
lck_attr_t *spt_lock_attr_;
lck_rw_t *spt_lock_;
lck_mtx_t *spt_add_lock_;
#else // KERNEL
void *spt_lock_grp_;
void *spt_lock_grp_attr_;
void *spt_lock_attr_;
#else // KERNEL
pthread_rwlock_t spt_lock_;
std::mutex *spt_add_lock_;
#endif // KERNEL
#endif // KERNEL
};
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */

View File

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

View File

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

View File

@@ -14,20 +14,20 @@
#import "Source/common/SNTRule.h"
@interface SNTRule()
@interface SNTRule ()
@property(readwrite) NSUInteger timestamp;
@end
@implementation SNTRule
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
self = [super init];
if (self) {
_shasum = shasum;
_identifier = identifier;
_state = state;
_type = type;
_customMsg = customMsg;
@@ -36,28 +36,24 @@
return self;
}
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithShasum:shasum
state:state
type:type
customMsg:customMsg
timestamp:0];
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithIdentifier:identifier state:state type:type customMsg:customMsg timestamp:0];
// Initialize timestamp to current time if rule is transitive.
if (self && state == SNTRuleStateWhitelistTransitive) {
if (self && state == SNTRuleStateAllowTransitive) {
[self resetTimestamp];
}
return self;
}
#pragma mark NSSecureCoding
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
#define ENCODE(obj, key) \
if (obj) [coder encodeObject:obj forKey:key]
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
+ (BOOL)supportsSecureCoding {
@@ -65,7 +61,7 @@
}
- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.shasum, @"shasum");
ENCODE(self.identifier, @"identifier");
ENCODE(@(self.state), @"state");
ENCODE(@(self.type), @"type");
ENCODE(self.customMsg, @"custommsg");
@@ -75,7 +71,7 @@
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_shasum = DECODE(NSString, @"shasum");
_identifier = DECODE(NSString, @"identifier");
_state = [DECODE(NSNumber, @"state") intValue];
_type = [DECODE(NSNumber, @"type") intValue];
_customMsg = DECODE(NSString, @"custommsg");
@@ -92,24 +88,25 @@
if (other == self) return YES;
if (![other isKindOfClass:[SNTRule class]]) return NO;
SNTRule *o = other;
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
return ([self.identifier isEqual:o.identifier] && self.state == o.state && self.type == o.type);
}
- (NSUInteger)hash {
NSUInteger prime = 31;
NSUInteger result = 1;
result = prime * result + [self.shasum hash];
result = prime * result + [self.identifier hash];
result = prime * result + self.state;
result = prime * result + self.type;
return result;
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
return [NSString
stringWithFormat:@"SNTRule: Identifier: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.identifier, self.state, self.type, (unsigned long)self.timestamp];
}
# pragma mark Last-access Timestamp
#pragma mark Last-access Timestamp
- (void)resetTimestamp {
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];

View File

@@ -19,7 +19,7 @@
///
/// Represents an event stored in the database.
///
@interface SNTStoredEvent : NSObject<NSSecureCoding>
@interface SNTStoredEvent : NSObject <NSSecureCoding>
///
/// An index for this event, randomly generated during initialization.

View File

@@ -21,11 +21,12 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
#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]
#define DECODEARRAY(cls, key) \
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
forKey:key]
+ (BOOL)supportsSecureCoding {
return YES;
@@ -129,7 +130,7 @@
- (NSString *)description {
return
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
}
#pragma clang diagnostic pop

View File

@@ -12,11 +12,10 @@
/// 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); \
#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);
#define WEAKIFY(var) __weak __typeof(var) Weak_##var = (var);

View File

@@ -17,12 +17,12 @@
@implementation SNTSystemInfo
+ (NSString *)serialNumber {
io_service_t platformExpert = IOServiceGetMatchingService(
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
io_service_t platformExpert =
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (!platformExpert) return nil;
NSString *serial = CFBridgingRelease(IORegistryEntryCreateCFProperty(
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
IOObjectRelease(platformExpert);
@@ -30,12 +30,12 @@
}
+ (NSString *)hardwareUUID {
io_service_t platformExpert = IOServiceGetMatchingService(
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
io_service_t platformExpert =
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (!platformExpert) return nil;
NSString *uuid = CFBridgingRelease(IORegistryEntryCreateCFProperty(
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
IOObjectRelease(platformExpert);
@@ -63,8 +63,8 @@
#pragma mark - Internal
+ (NSDictionary *)_systemVersionDictionary {
return [NSDictionary
dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
return
[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
}
@end

View File

@@ -42,7 +42,8 @@ typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNu
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
///
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice know you are done with it.
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice
/// know you are done with it.
///
- (void)spindown;

View File

@@ -22,9 +22,9 @@
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(hashBundleBinariesForEvent:reply:)
argumentIndex:1
ofReply:YES];
forSelector:@selector(hashBundleBinariesForEvent:reply:)
argumentIndex:1
ofReply:YES];
return r;
}

View File

@@ -34,6 +34,7 @@
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply;
///
@@ -44,10 +45,10 @@
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setAllowedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;
///
/// Syncd Ops

View File

@@ -42,14 +42,14 @@ NSString *const kBundleID = @"com.google.santa.daemon";
+ (void)initializeControlInterface:(NSXPCInterface *)r {
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(databaseEventsPending:)
argumentIndex:0
ofReply:YES];
forSelector:@selector(databaseEventsPending:)
argumentIndex:0
ofReply:YES];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTRule class], nil]
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
argumentIndex:0
ofReply:NO];
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
argumentIndex:0
ofReply:NO];
}
+ (NSXPCInterface *)controlInterface {

View File

@@ -0,0 +1,50 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
/// Protocol implemented by the metric service and utilized by santad
/// exporting metrics to a monitoring system.
@protocol SNTMetricServiceXPC
///
/// @param metrics The current metric/counter values serialized to an NSDictionary.
///
- (void)exportForMonitoring:(NSDictionary *)metrics;
@end
@interface SNTXPCMetricServiceInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTMetricServiceXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up
/// before returning.
///
+ (NSXPCInterface *)metricServiceInterface;
///
/// Returns the MachService ID for this service.
///
+ (NSString *)serviceID;
///
/// Retrieve a pre-configured MOLXPCConnection for communicating with santametricservice.
/// Connections just needs any handlers set and then can be resumed and used.
///
+ (MOLXPCConnection *)configuredConnection;
@end

View File

@@ -0,0 +1,42 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCMetricServiceInterface.h"
@implementation SNTXPCMetricServiceInterface
+ (NSXPCInterface *)metricServiceInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTMetricServiceXPC)];
[r setClasses:[NSSet setWithObjects:[NSDictionary class], [NSArray class], [NSNumber class],
[NSString class], [NSDate class], nil]
forSelector:@selector(exportForMonitoring:)
argumentIndex:0
ofReply:NO];
return r;
}
+ (NSString *)serviceID {
return @"com.google.santa.metricservice";
}
+ (MOLXPCConnection *)configuredConnection {
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
privileged:NO];
c.remoteInterface = [self metricServiceInterface];
return c;
}
@end

View File

@@ -0,0 +1,56 @@
/// Copyright 2020 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
@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;
@end
@interface SNTXPCSyncServiceInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTSyncServiceXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
///
+ (NSXPCInterface *)syncServiceInterface;
///
/// Returns the MachService ID for this service.
///
+ (NSString *)serviceID;
///
/// Retrieve a pre-configured MOLXPCConnection for communicating with syncservice.
/// Connections just needs any handlers set and then can be resumed and used.
///
+ (MOLXPCConnection *)configuredConnection;
@end

View File

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

View File

@@ -22,9 +22,9 @@
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
argumentIndex:0
ofReply:NO];
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
argumentIndex:0
ofReply:NO];
return r;
}

View File

@@ -38,10 +38,8 @@
///
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary,
int64_t certificate,
int64_t compiler,
int64_t transitive))reply;
- (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;
///
@@ -59,6 +57,7 @@
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTEventState))reply;
///
@@ -71,7 +70,12 @@
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)syncCleanRequired:(void (^)(BOOL))reply;
- (void)enableBundles:(void (^)(BOOL))reply;
- (void)enableTransitiveWhitelisting:(void (^)(BOOL))reply;
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
///
/// Metrics ops
///
- (void)metrics:(void (^)(NSDictionary *))reply;
///
/// GUI Ops

View File

@@ -23,13 +23,14 @@
+ (void)initializeControlInterface:(NSXPCInterface *)r {
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(syncBundleEvent:relatedEvents:)
argumentIndex:1
ofReply:NO];
forSelector:@selector(syncBundleEvent:relatedEvents:)
argumentIndex:1
ofReply:NO];
}
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
NSXPCInterface *r =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
[self initializeControlInterface:r];
return r;

View File

@@ -24,20 +24,24 @@
#ifdef KERNEL
#include <IOKit/IOLib.h>
#else // KERNEL
#else // KERNEL
// Support for unit testing.
#include <cstdio>
#include <cstdlib>
#include <cstring>
// TODO(rah): Consider templatizing these.
#define panic(args...) printf(args); printf("\n"); abort()
#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)
#endif // KERNEL
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif // KERNEL
/**
A type to specialize to help SantaCache with its hashing.
@@ -45,12 +49,14 @@
The default works for numeric types with a multiplicative hash
using a prime near to the golden ratio, per Knuth.
*/
template<typename T> uint64_t SantaCacheHasher(T const& t) {
return t * 11400714819323198549UL;
template <typename T>
uint64_t SantaCacheHasher(T const &t) {
return (uint64_t)t * 11400714819323198549UL;
};
/**
A somewhat simple, concurrent linked-list hash table intended for use in IOKit kernel extensions.
A somewhat simple, concurrent linked-list hash table intended for use in IOKit
kernel extensions.
The type used for keys must overload the == operator and a specialization of
SantaCacheHasher must exist for it.
@@ -61,24 +67,29 @@ template<typename T> uint64_t SantaCacheHasher(T const& t) {
The number of buckets is calculated as `maximum_size` / `per_bucket`
rounded up to the next power of 2. Locking is done per-bucket.
*/
template<typename KeyT, typename ValueT> class SantaCache {
template <typename KeyT, typename ValueT>
class SantaCache {
public:
/**
Initialize a newly created cache.
@param maximum_size The maximum number of entries in this cache. Once this
number is reached all the entries will be purged.
@param per_bucket The target number of entries in each bucket when cache is full.
A higher number will result in better performance but higher memory usage.
Cannot be higher than 64 to try and ensure buckets don't overflow.
@param per_bucket The target number of entries in each bucket when cache is
full. A higher number will result in better performance but higher memory
usage. Cannot be higher than 64 to try and ensure buckets don't overflow.
*/
SantaCache(uint64_t maximum_size = 10000, uint8_t per_bucket = 5) {
if (unlikely(per_bucket > maximum_size)) per_bucket = maximum_size;
if (unlikely(per_bucket > maximum_size)) per_bucket = (uint8_t)maximum_size;
if (unlikely(per_bucket < 1)) per_bucket = 1;
if (unlikely(per_bucket > 64)) per_bucket = 64;
max_size_ = maximum_size;
bucket_count_ = (1 << (32 - __builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
buckets_ = (struct bucket *)IOMallocAligned(bucket_count_ * sizeof(struct bucket), 2);
bucket_count_ =
(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);
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
}
@@ -120,7 +131,7 @@ template<typename KeyT, typename ValueT> class SantaCache {
@return true if the value was set.
*/
bool set(const KeyT& key, const ValueT& value) {
bool set(const KeyT &key, const ValueT &value) {
return set(key, value, {}, false);
}
@@ -138,16 +149,14 @@ template<typename KeyT, typename ValueT> class SantaCache {
@return true if the value was set
*/
bool set(const KeyT& key, const ValueT& value, const ValueT& previous_value) {
bool set(const KeyT &key, const ValueT &value, const ValueT &previous_value) {
return set(key, value, previous_value, true);
}
/**
An alias for `set(key, zero_)`
*/
inline void remove(const KeyT& key) {
set(key, zero_);
}
inline void remove(const KeyT &key) { set(key, zero_); }
/**
Remove all entries and free bucket memory.
@@ -181,24 +190,31 @@ template<typename KeyT, typename ValueT> class SantaCache {
/**
Return number of entries currently in cache.
*/
inline uint64_t count() const {
return count_;
}
inline uint64_t count() const { return count_; }
/**
Fill in the per_bucket_counts array with the number of entries in each bucket.
Fill in the per_bucket_counts array with the number of entries in each
bucket.
The per_buckets_count array will contain the per-bucket counts, up to the number
in array_size. The start_bucket parameter will determine which bucket to start off
with and upon return will contain either 0 if no buckets are remaining or the next
bucket to begin with when called again.
The per_buckets_count array will contain the per-bucket counts, up to the
number in array_size. The start_bucket parameter will determine which bucket
to start off with and upon return will contain either 0 if no buckets are
remaining or the next bucket to begin with when called again.
*/
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
if (per_bucket_counts == nullptr || array_size == nullptr || start_bucket == nullptr) return;
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size,
uint64_t *start_bucket) {
if (per_bucket_counts == nullptr || array_size == nullptr ||
start_bucket == nullptr)
return;
uint64_t start = *start_bucket;
if (start >= bucket_count_) {
*start_bucket = 0;
return;
}
uint16_t size = *array_size;
if (start + size > bucket_count_) size = bucket_count_ - start;
if (start + size > bucket_count_) size = (uint16_t)(bucket_count_ - start);
for (uint16_t i = 0; i < size; ++i) {
uint16_t count = 0;
@@ -245,8 +261,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
@return true if the entry was set, false if it was not
*/
bool set(const KeyT& key, const ValueT& value,
const ValueT& previous_value, bool has_prev_value) {
bool set(const KeyT &key, const ValueT &value, const ValueT &previous_value,
bool has_prev_value) {
struct bucket *bucket = &buckets_[hash(key)];
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
@@ -302,7 +318,8 @@ template<typename KeyT, typename ValueT> 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 *)IOMallocAligned(sizeof(struct entry), 2);
bzero(new_entry, sizeof(struct entry));
new_entry->key = key;
new_entry->value = value;
@@ -318,7 +335,8 @@ template<typename KeyT, typename ValueT> 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 (OSTestAndSet(7, (volatile uint8_t *)&bucket->head))
;
}
/**
@@ -357,4 +375,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
}
};
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
#ifndef KERNEL
#pragma clang diagnostic pop
#endif
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H

View File

@@ -18,7 +18,7 @@
#include <string>
#include <vector>
#include "Source/santa_driver/SantaCache.h"
#include "Source/common/SantaCache.h"
@interface SantaCacheTest : XCTestCase
@end
@@ -102,9 +102,8 @@
// Calculate stdev
double accum = 0.0;
std::for_each(per_bucket.begin(), per_bucket.end(), [&](const double d) {
accum += (d - mean) * (d - mean);
});
std::for_each(per_bucket.begin(), per_bucket.end(),
[&](const double d) { accum += (d - mean) * (d - mean); });
double stddev = sqrt(accum / (per_bucket.size() - 1));
double maxStdDev = (double)br / 2;
XCTAssertLessThanOrEqual(stddev, maxStdDev,
@@ -143,13 +142,15 @@
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
for (int i = 0; i < 5000; ++i) sut->set(i, 10000-i);
for (int i = 0; i < 5000; ++i)
sut->set(i, 10000 - i);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
for (int i = 5000; i < 10000; ++i) sut->set(i, 10000-i);
for (int i = 5000; i < 10000; ++i)
sut->set(i, 10000 - i);
dispatch_group_leave(group);
});
@@ -157,7 +158,8 @@
XCTFail("Timed out while setting values for test");
}
for (int i = 0; i < 10000; ++i) XCTAssertEqual(sut->get(i), 10000 - i);
for (int i = 0; i < 10000; ++i)
XCTAssertEqual(sut->get(i), 10000 - i);
}
delete sut;
@@ -193,7 +195,8 @@
XCTAssertEqual(sut.get(3.1459124), 0);
}
template<> uint64_t SantaCacheHasher<std::string>(std::string const& s) {
template <>
uint64_t SantaCacheHasher<std::string>(std::string const &s) {
return std::hash<std::string>{}(s);
}
@@ -242,16 +245,17 @@ struct S {
uint64_t first_val;
uint64_t second_val;
bool operator==(const S& rhs) {
bool operator==(const S &rhs) {
return first_val == rhs.first_val && second_val == rhs.second_val;
}
};
template<> uint64_t SantaCacheHasher<S>(S const& s) {
template <>
uint64_t SantaCacheHasher<S>(S const &s) {
return SantaCacheHasher<uint64_t>(s.first_val) ^ (SantaCacheHasher<uint64_t>(s.second_val) << 1);
}
- (void)testStructKeys {
auto sut = SantaCache<S, uint64_t>(10, 2);
auto sut = SantaCache<S, uint64_t>(10, 2);
S s1 = {1024, 2048};
S s2 = {4096, 8192};
@@ -265,4 +269,22 @@ template<> uint64_t SantaCacheHasher<S>(S const& s) {
XCTAssertEqual(sut.get(s3), 30);
}
- (void)testBucketCounts {
auto sut = new SantaCache<uint64_t, uint64_t>(UINT16_MAX, 1);
// These tests verify that the bucket_counts() function can't be abused
// with integer {over,under}flow issues in the input or going out-of-bounds
// on the buckets array.
uint16_t size = 2048;
uint64_t start = (UINT64_MAX - 2047);
uint16_t per_bucket_counts[2048];
sut->bucket_counts(per_bucket_counts, &size, &start);
XCTAssertEqual(start, 0, @"Check a high start can't overflow");
size = UINT16_MAX;
start = UINT16_MAX - 1;
sut->bucket_counts(per_bucket_counts, &size, &start);
XCTAssertEqual(start, 0, @"Check a large size can't overflow");
}
@end

BIN
Source/common/testdata/32bitplist vendored Executable file

Binary file not shown.

View File

@@ -1,11 +1,11 @@
licenses(["notice"]) # Apache 2.0
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
licenses(["notice"])
exports_files([
"Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-256.png",
])
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
objc_library(
name = "SantaGUI_lib",
srcs = [
@@ -35,6 +35,7 @@ objc_library(
deps = [
"//Source/common:SNTBlockMessage_SantaGUI",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCNotifierInterface",
"@MOLCodesignChecker",
@@ -47,14 +48,25 @@ macos_application(
additional_contents = {
"//Source/santactl": "MacOS",
"//Source/santabundleservice": "MacOS",
"//Source/santametricservice": "MacOS",
"//Source/santad:com.google.santa.daemon": "Library/SystemExtensions",
},
app_icons = glob(["Resources/Images.xcassets/**"]),
bundle_id = "com.google.santa",
bundle_name = "Santa",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
entitlements = "Santa.app.entitlements",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//visibility:public"],
visibility = ["//:santa_package_group"],
deps = [":SantaGUI_lib"],
)

View File

@@ -7,6 +7,7 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
<connections>
<outlet property="aboutTextField" destination="uh6-q0-RzL" id="oGn-hV-wwc"/>
<outlet property="moreInfoButton" destination="SRu-Kf-vu5" id="Vj2-9Q-05d"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
@@ -38,7 +39,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="CcT-ul-1eA">
<font key="font" metaFont="system"/>
<string key="title">Santa is an application whitelisting system for macOS.
<string key="title">Santa is an application control system for macOS.
There are no user-configurable settings.</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>

View File

@@ -3,4 +3,4 @@
"version" : 1,
"author" : "xcode"
}
}
}

View File

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

View File

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

View File

@@ -17,5 +17,5 @@
///
/// Initiates and manages the connection to santad
///
@interface SNTAppDelegate : NSObject<NSApplicationDelegate>
@interface SNTAppDelegate : NSObject <NSApplicationDelegate>
@end

View File

@@ -16,8 +16,6 @@
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <SystemExtensions/SystemExtensions.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStrengthify.h"
@@ -25,7 +23,7 @@
#import "Source/santa/SNTAboutWindowController.h"
#import "Source/santa/SNTNotificationManager.h"
@interface SNTAppDelegate ()<OSSystemExtensionRequestDelegate>
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;
@property SNTNotificationManager *notificationManager;
@property MOLXPCConnection *daemonListener;
@@ -36,10 +34,6 @@
#pragma mark App Delegate methods
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
if (@available(macOS 10.15, *)) {
[self loadSystemExtension];
}
[self setupMenu];
self.notificationManager = [[SNTNotificationManager alloc] init];
@@ -49,16 +43,16 @@
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
self.daemonListener.invalidationHandler = nil;
[self.daemonListener invalidate];
self.daemonListener = nil;
}];
self.daemonListener.invalidationHandler = nil;
[self.daemonListener invalidate];
self.daemonListener = nil;
}];
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
[self attemptDaemonReconnection];
}];
[self attemptDaemonReconnection];
}];
[self createDaemonConnection];
}
@@ -69,20 +63,6 @@
return NO;
}
- (void)loadSystemExtension API_AVAILABLE(macos(10.15)) {
if (![[SNTConfigurator configurator] enableSystemExtension]) {
LOGI(@"EnableSystemExtension is disabled");
return;
}
LOGI(@"Requesting SystemExtension activation");
NSString *e = [SNTXPCControlInterface systemExtensionID];
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
OSSystemExtensionRequest *req = [OSSystemExtensionRequest activationRequestForExtension:e
queue:q];
req.delegate = self;
[[OSSystemExtensionManager sharedManager] submitRequest:req];
}
#pragma mark Connection handling
- (void)createDaemonConnection {
@@ -143,28 +123,4 @@
[NSApp setMainMenu:mainMenu];
}
#pragma mark OSSystemExtensionRequestDelegate
- (OSSystemExtensionReplacementAction)request:(OSSystemExtensionRequest *)request
actionForReplacingExtension:(OSSystemExtensionProperties *)old
withExtension:(OSSystemExtensionProperties *)new
API_AVAILABLE(macos(10.15)) {
LOGI(@"SystemExtension \"%@\" request for replacement", request.identifier);
return OSSystemExtensionReplacementActionReplace;
}
- (void)requestNeedsUserApproval:(OSSystemExtensionRequest *)request API_AVAILABLE(macos(10.15)) {
LOGI(@"SystemExtension \"%@\" request needs user approval", request.identifier);
}
- (void)request:(OSSystemExtensionRequest *)request
didFailWithError:(NSError *)error API_AVAILABLE(macos(10.15)) {
LOGI(@"SystemExtension \"%@\" request did fail: %@", request.identifier, error);
}
- (void)request:(OSSystemExtensionRequest *)request
didFinishWithResult:(OSSystemExtensionRequestResult)result API_AVAILABLE(macos(10.15)) {
LOGI(@"SystemExtension \"%@\" request did finish: %ld", request.identifier, (long)result);
}
@end

View File

@@ -20,7 +20,7 @@
///
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.
///
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate, SNTNotifierXPC>
@interface SNTNotificationManager : NSObject <SNTMessageWindowControllerDelegate, SNTNotifierXPC>
@property NSXPCListenerEndpoint *notificationListener;

View File

@@ -38,14 +38,14 @@
@implementation SNTNotificationManager
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
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);
_hashBundleBinariesQueue =
dispatch_queue_create("com.google.santagui.hashbundlebinaries", DISPATCH_QUEUE_SERIAL);
}
return self;
}
@@ -107,8 +107,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
break;
default:
return;
default: return;
}
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
@@ -116,7 +115,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
- (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];
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
// See if this binary is silenced.
@@ -145,7 +144,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
// This includes making windows.
dispatch_async(dispatch_get_main_queue(), ^{
SNTMessageWindowController *pendingMsg =
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
@@ -179,8 +178,8 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
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"];
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
});
}
}
@@ -210,34 +209,39 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
[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];
[[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;
}
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];
// 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];
// Update the UI with the bundle hash. Also make the openEventButton
// available.
[self updateBlockNotification:event withBundleHash:bh];
[bc invalidate];
}];
[bc invalidate];
}];
[self.currentWindowController.progress resignCurrent];
}

View File

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

View File

@@ -19,7 +19,7 @@
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santa/SNTAppDelegate.h"
@interface SNTSystemExtensionDelegate : NSObject<OSSystemExtensionRequestDelegate>
@interface SNTSystemExtensionDelegate : NSObject <OSSystemExtensionRequestDelegate>
@end
@implementation SNTSystemExtensionDelegate
@@ -28,8 +28,8 @@
- (OSSystemExtensionReplacementAction)request:(OSSystemExtensionRequest *)request
actionForReplacingExtension:(OSSystemExtensionProperties *)old
withExtension:(OSSystemExtensionProperties *)new
API_AVAILABLE(macos(10.15)) {
withExtension:
(OSSystemExtensionProperties *)new API_AVAILABLE(macos(10.15)) {
NSLog(@"SystemExtension \"%@\" request for replacement", request.identifier);
return OSSystemExtensionReplacementActionReplace;
}
@@ -39,13 +39,13 @@
}
- (void)request:(OSSystemExtensionRequest *)request
didFailWithError:(NSError *)error API_AVAILABLE(macos(10.15)) {
didFailWithError:(NSError *)error API_AVAILABLE(macos(10.15)) {
NSLog(@"SystemExtension \"%@\" request did fail: %@", request.identifier, error);
exit((int)error.code);
}
- (void)request:(OSSystemExtensionRequest *)request
didFinishWithResult:(OSSystemExtensionRequestResult)result API_AVAILABLE(macos(10.15)) {
didFinishWithResult:(OSSystemExtensionRequestResult)result API_AVAILABLE(macos(10.15)) {
NSLog(@"SystemExtension \"%@\" request did finish: %ld", request.identifier, (long)result);
exit(0);
}

View File

@@ -1,17 +1,16 @@
licenses(["notice"]) # Apache 2.0
load(
"@build_bazel_rules_apple//apple:macos.bzl",
"macos_command_line_application",
"macos_kernel_extension",
)
load("//:helper.bzl", "run_command", "santa_unit_test")
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
licenses(["notice"])
cc_library(
name = "santa_driver_lib",
srcs = [
"SantaCache.h",
"SantaDecisionManager.cc",
"SantaDecisionManager.h",
"SantaDriver.cc",
@@ -23,6 +22,7 @@ cc_library(
copts = [
"-mkernel",
"-fapple-kext",
"-Wno-ossharedptr-misuse",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = [
@@ -35,19 +35,11 @@ cc_library(
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLoggingKernel",
"//Source/common:SNTPrefixTreeKernel",
"//Source/common:SantaCache",
],
alwayslink = 1,
)
santa_unit_test(
name = "SantaCacheTest",
srcs = [
"SantaCache.h",
"SantaCacheTest.mm",
],
deps = ["//Source/common:SNTKernelCommon"],
)
macos_kernel_extension(
name = "santa_driver",
bundle_id = "com.google.santa-driver",
@@ -55,7 +47,7 @@ macos_kernel_extension(
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
version = "//:version",
visibility = ["//visibility:public"],
visibility = ["//:santa_package_group"],
deps = [":santa_driver_lib"],
)

View File

@@ -185,23 +185,35 @@ void SantaDecisionManager::SetLogPort(mach_port_t port) {
}
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
return decision_dataqueue_->getMemoryDescriptor();
lck_mtx_lock(decision_dataqueue_lock_);
IOMemoryDescriptor *r = decision_dataqueue_->getMemoryDescriptor();
lck_mtx_unlock(decision_dataqueue_lock_);
return r;
}
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
return log_dataqueue_->getMemoryDescriptor();
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.");
@@ -210,11 +222,14 @@ kern_return_t SantaDecisionManager::StartListener() {
}
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 {
@@ -601,7 +616,7 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
pid_t pid = proc_pid(proc);
pid_t ppid = proc_ppid(proc);
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
uint64_t val = ((uint64_t)pid << 32) | (ppid & 0xFFFFFFFF);
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:
@@ -659,8 +674,8 @@ void SantaDecisionManager::FileOpCallback(
uint64_t val = vnode_pid_map_->get(vnode_id);
if (val) {
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
message->pid = (val >> 32);
message->ppid = (val & ~0xFFFFFFFF00000000);
message->pid = (pid_t)(val >> 32);
message->ppid = (pid_t)(val & ~0xFFFFFFFF00000000);
}
PostToLogQueue(message);
@@ -801,7 +816,7 @@ extern "C" int vnode_scope_callback(
// We only care about regular files.
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
if ((action & KAUTH_VNODE_EXECUTE) && !(action & KAUTH_VNODE_ACCESS)) { // NOLINT
if ((action & (int)KAUTH_VNODE_EXECUTE) && !(action & (int)KAUTH_VNODE_ACCESS)) {
sdm->IncrementListenerInvocations();
int result = sdm->VnodeCallback(credential,
reinterpret_cast<vfs_context_t>(arg0),
@@ -809,9 +824,9 @@ extern "C" int vnode_scope_callback(
reinterpret_cast<int *>(arg3));
sdm->DecrementListenerInvocations();
return result;
} else if (action & KAUTH_VNODE_WRITE_DATA || action & KAUTH_VNODE_APPEND_DATA) {
} else if (action & (int)KAUTH_VNODE_WRITE_DATA || action & (int)KAUTH_VNODE_APPEND_DATA) {
sdm->IncrementListenerInvocations();
if (!(action & KAUTH_VNODE_ACCESS)) { // NOLINT
if (!(action & (int)KAUTH_VNODE_ACCESS)) {
auto vnode_id = sdm->GetVnodeIDForVnode(reinterpret_cast<vfs_context_t>(arg0), vp);
sdm->RemoveFromCache(vnode_id);
}

View File

@@ -26,7 +26,7 @@
#include "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTLogging.h"
#include "Source/common/SNTPrefixTree.h"
#include "Source/santa_driver/SantaCache.h"
#include "Source/common/SantaCache.h"
///
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
@@ -109,8 +109,7 @@ class SantaDecisionManager : public OSObject {
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,
void AddToCache(santa_vnode_id_t identifier, const santa_action_t decision,
const uint64_t microsecs = GetCurrentUptime());
/**
@@ -133,17 +132,20 @@ class SantaDecisionManager : public OSObject {
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.
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.
@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);
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size,
uint64_t *start_bucket);
/// Increments the count of active callbacks pending.
void IncrementListenerInvocations();
@@ -167,16 +169,15 @@ class SantaDecisionManager : public OSObject {
/**
Add a file modification prefix filter.
*/
inline IOReturn FilemodPrefixFilterAdd(const char *prefix, uint64_t *node_count = nullptr) {
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();
}
inline void FilemodPrefixFilterReset() { filemod_prefix_filter_->Reset(); }
/**
Fetches the vnode_id for a given vnode.
@@ -185,16 +186,14 @@ class SantaDecisionManager : public OSObject {
@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) {
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
};
return {.fsid = vap.va_fsid, .fileid = vap.va_fileid};
}
/**
@@ -216,8 +215,8 @@ class SantaDecisionManager : public OSObject {
@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);
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path,
const char *new_path);
private:
/**
@@ -227,8 +226,8 @@ class SantaDecisionManager : public OSObject {
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
/**
While waiting for a response from the daemon, this is the maximum number cache checks before
re-sending the request.
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;
@@ -274,7 +273,8 @@ class SantaDecisionManager : public OSObject {
@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);
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
@@ -287,8 +287,8 @@ class SantaDecisionManager : public OSObject {
@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);
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.
@@ -355,7 +355,8 @@ class SantaDecisionManager : public OSObject {
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<santa_vnode_id_t, uint64_t>* CacheForIdentifier(const santa_vnode_id_t identifier);
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
@@ -381,8 +382,9 @@ class SantaDecisionManager : public OSObject {
kauth_listener_t vnode_listener_;
kauth_listener_t fileop_listener_;
struct timespec ts_= { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
struct timespec ts_ = {
.tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000};
};
/**
@@ -396,14 +398,10 @@ class SantaDecisionManager : public OSObject {
@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);
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
@@ -416,13 +414,9 @@ extern "C" int vnode_scope_callback(
@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);
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

@@ -293,7 +293,7 @@ IOReturn SantaDriverClient::externalMethod(
{ &SantaDriverClient::filemod_prefix_filter_reset, 0, 0, 0, 0 },
};
if (selector > static_cast<UInt32>(kSantaUserClientNMethods)) {
if (selector >= static_cast<UInt32>(kSantaUserClientNMethods)) {
return kIOReturnBadArgument;
}

View File

@@ -54,24 +54,23 @@ class com_google_SantaDriverClient : public IOUserClient {
IOReturn clientDied() override;
/// Called during termination
bool didTerminate(IOService* provider, IOOptionBits options, bool* defer) override;
bool didTerminate(IOService *provider, IOOptionBits options,
bool *defer) override;
/// Called in clients with IOConnectSetNotificationPort
IOReturn registerNotificationPort(
mach_port_t port, UInt32 type, UInt32 refCon) override;
IOReturn registerNotificationPort(mach_port_t port, UInt32 type,
UInt32 refCon) override;
/// Called in clients with IOConnectMapMemory
IOReturn clientMemoryForType(
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) override;
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options,
IOMemoryDescriptor **memory) override;
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
/// to the requested selector using the SantaDriverMethods enum in
/// SNTKernelCommon.
IOReturn externalMethod(
UInt32 selector,
IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch,
OSObject *target, void *reference) override;
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch, OSObject *target,
void *reference) override;
///
/// The userspace callable methods are below. Each method corresponds
@@ -79,47 +78,47 @@ class com_google_SantaDriverClient : public IOUserClient {
///
/// Called during client connection.
static IOReturn open(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn open(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a binary.
static IOReturn allow_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn allow_binary(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a compiler binary.
static IOReturn allow_compiler(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn allow_compiler(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to deny a binary.
static IOReturn deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn deny_binary(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to acknowledge a binary request. This is used for large binaries that
/// may take a while to reach a decision.
static IOReturn acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to acknowledge a binary request. This is used for
/// large binaries that may take a while to reach a decision.
static IOReturn acknowledge_binary(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to empty the cache.
static IOReturn clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn clear_cache(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon call this to remove a single cache entry.
static IOReturn remove_cache_entry(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn remove_cache_entry(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in the cache
static IOReturn cache_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn cache_count(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out the status of a vnode_id in the cache.
/// Output will be a santa_action_t.
static IOReturn check_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
static IOReturn check_cache(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in each cache bucket.
/// Input and output are both an instance of santa_bucket_count_t.
static IOReturn cache_bucket_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in each cache
/// bucket. Input and output are both an instance of santa_bucket_count_t.
static IOReturn cache_bucket_count(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to add filemod prefix filters at daemon startup.
static IOReturn filemod_prefix_filter_add(

View File

@@ -19,14 +19,14 @@
#import <CommonCrypto/CommonDigest.h>
#include <libkern/OSKextLib.h>
#include <mach/mach.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <cmath>
#include <ctime>
#include <iostream>
#include <libkern/OSKextLib.h>
#include <mach/mach.h>
#include <numeric>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <vector>
#include "Source/common/SNTKernelCommon.h"
@@ -39,24 +39,30 @@
/// any existing driver (and daemon) if necessary.
///
#define TSTART(testName) \
do { printf(" %-50s ", testName); } while (0)
#define TPASS() \
do { printf("PASS\n"); } while (0)
#define TPASSINFO(fmt, ...) \
do { printf("PASS\n " fmt "\n", ##__VA_ARGS__); } while (0)
#define TFAIL() \
do { \
printf("FAIL\n"); \
[self unloadExtension]; \
exit(1); \
} while (0)
#define TFAILINFO(fmt, ...) \
do { \
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
[self unloadExtension]; \
exit(1); \
} while (0)
#define TSTART(testName) \
do { \
printf(" %-50s ", testName); \
} while (0)
#define TPASS() \
do { \
printf("PASS\n"); \
} while (0)
#define TPASSINFO(fmt, ...) \
do { \
printf("PASS\n " fmt "\n", ##__VA_ARGS__); \
} while (0)
#define TFAIL() \
do { \
printf("FAIL\n"); \
[self unloadExtension]; \
exit(1); \
} while (0)
#define TFAILINFO(fmt, ...) \
do { \
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
[self unloadExtension]; \
exit(1); \
} while (0)
@interface SantaKernelTests : NSObject
@property io_connect_t connection;
@@ -88,9 +94,7 @@
- (NSString *)sha256ForPath:(NSString *)path {
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
NSData *fData = [NSData dataWithContentsOfFile:path
options:NSDataReadingMappedIfSafe
error:nil];
NSData *fData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:nil];
CC_SHA256([fData bytes], (unsigned int)[fData length], sha256);
char buf[CC_SHA256_DIGEST_LENGTH * 2 + 1];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
@@ -105,7 +109,7 @@
static NSString *path;
if (!path) {
NSTask *xcrun = [self taskWithPath:@"/usr/bin/xcrun"];
xcrun.arguments = @[@"-f", @"ld"];
xcrun.arguments = @[ @"-f", @"ld" ];
xcrun.standardOutput = [NSPipe pipe];
@try {
[xcrun launch];
@@ -117,7 +121,7 @@
NSData *data = [[xcrun.standardOutput fileHandleForReading] readDataToEndOfFile];
if (!data) return nil;
path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
return path;
}
@@ -129,20 +133,20 @@
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeid {
switch (action) {
case ACTION_RESPOND_ALLOW:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_DENY:
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ACK:
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ALLOW_COMPILER:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler,
&vnodeid, sizeof(vnodeid), 0, 0);
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
default:
TFAILINFO("postToKernelAction:forVnodeID: received unknown action type: %d", action);
@@ -230,8 +234,8 @@
TPASS();
TSTART("Registers the notification port");
kern_return_t kr = IOConnectSetNotificationPort(
self.connection, QUEUETYPE_DECISION, receivePort, 0);
kern_return_t kr =
IOConnectSetNotificationPort(self.connection, QUEUETYPE_DECISION, receivePort, 0);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), receivePort);
TFAILINFO("KR: %d", kr);
@@ -242,8 +246,8 @@
TSTART("Maps shared memory");
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(),
&address, &size, kIOMapAnywhere);
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(), &address, &size,
kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), receivePort);
TFAILINFO("KR: %d", kr);
@@ -292,8 +296,7 @@
[ed launch];
[ed waitUntilExit];
TFAIL();
}
@catch (NSException *exception) {
} @catch (NSException *exception) {
TPASS();
}
}
@@ -331,7 +334,7 @@
NSFileManager *fm = [NSFileManager defaultManager];
NSString *target =
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
[fm removeItemAtPath:target error:nil];
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
@@ -360,7 +363,7 @@
}
// Now replace the contents of the test file (which is cached) with the contents of /bin/ed,
// which is 'blacklisted' by SHA-256 during the tests.
// which is blocked by SHA-256 during the tests.
FILE *infile = fopen("/bin/ed", "r");
FILE *outfile = fopen(target.UTF8String, "w");
int ch;
@@ -430,10 +433,9 @@
// Replace file contents using dd, which doesn't close FDs
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
NSTask *dd = [self taskWithPath:@"/bin/dd"];
dd.arguments = @[ @"if=/bin/ed",
@"of=invalidacachetest_tmp",
@"bs=1",
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
dd.arguments = @[
@"if=/bin/ed", @"of=invalidacachetest_tmp", @"bs=1",
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
];
[dd launch];
[dd waitUntilExit];
@@ -494,7 +496,8 @@
TFAILINFO("Failed to fork");
} else if (pid > 0) {
int status;
while (waitpid(pid, &status, 0) != pid); // handle EINTR
while (waitpid(pid, &status, 0) != pid)
; // handle EINTR
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
TPASS();
} else if (WIFSTOPPED(status)) {
@@ -531,17 +534,16 @@
// Sort and remove first 10 and last 10 entries.
std::sort(times.begin(), times.end());
times.erase(times.begin(), times.begin()+10);
times.erase(times.end()-10, times.end());
times.erase(times.begin(), times.begin() + 10);
times.erase(times.end() - 10, times.end());
// Calculate mean
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
// Calculate stdev
double accum = 0.0;
std::for_each(times.begin(), times.end(), [&](const double d) {
accum += (d - mean) * (d - mean);
});
std::for_each(times.begin(), times.end(),
[&](const double d) { accum += (d - mean) * (d - mean); });
double stdev = sqrt(accum / (times.size() - 1));
if (mean > 80 || stdev > 10) {
@@ -562,8 +564,8 @@
if (calCount++) TFAILINFO("Large binary should not re-request");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
dispatch_get_global_queue(0, 0), ^{
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
});
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
});
return ACTION_RESPOND_ACK;
}
return ACTION_RESPOND_ALLOW;
@@ -613,7 +615,7 @@
fclose(out);
// Then compile it with clang and ld, the latter of which has been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[@"-o", @"/private/tmp/hello", @"/private/tmp/hello.c"];
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
[clang launch];
[clang waitUntilExit];
// Make sure that our version of ld marked as compiler was run. This assumes that
@@ -672,7 +674,7 @@
fclose(out);
// Then compile it with clang and ld, neither of which have been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[@"-o", @"/private/tmp/hello", @"/private/tmp/hello.c"];
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
@try {
[clang launch];
[clang waitUntilExit];
@@ -709,9 +711,9 @@
TSTART("Testing filemod prefix filter");
NSString *filter =
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
@"are the same for all non-accelerating observers, and he showed that the speed of light "
@"within a vacuum is the same no matter the speed at which an observer travels.";
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
@"are the same for all non-accelerating observers, and he showed that the speed of light "
@"within a vacuum is the same no matter the speed at which an observer travels.";
// Create a buffer that has 1024 bytes of characters, non-terminating.
char buffer[MAXPATHLEN + 1]; // +1 is for the null byte from strlcpy(). It falls off the ledge.
@@ -728,8 +730,8 @@
// The filter should currently be empty. It is reset when the client disconnects.
// Fill up the 1024 node capacity.
kern_return_t ret =
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess || n != 1024) {
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
@@ -794,15 +796,15 @@
NSString *src = [[fm currentDirectoryPath] stringByAppendingPathComponent:@"santa-driver.kext"];
NSString *dest = [NSTemporaryDirectory() stringByAppendingPathComponent:@"santa-driver.kext"];
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
if (![fm copyItemAtPath:src toPath:dest error:&error] || error) {
TFAILINFO("Failed to copy kext: %s", error.description.UTF8String);
}
NSDictionary *attrs = @{
NSFileOwnerAccountName : @"root",
NSFileGroupOwnerAccountName : @"wheel",
NSFilePosixPermissions : @0755
NSFileOwnerAccountName : @"root",
NSFileGroupOwnerAccountName : @"wheel",
NSFilePosixPermissions : @0755
};
[fm setAttributes:attrs ofItemAtPath:dest error:NULL];

View File

@@ -1,6 +1,6 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
licenses(["notice"]) # Apache 2.0
licenses(["notice"])
objc_library(
name = "santabs_lib",
@@ -26,6 +26,6 @@ macos_command_line_application(
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
version = "//:version",
visibility = ["//visibility:public"],
visibility = ["//:santa_package_group"],
deps = [":santabs_lib"],
)

View File

@@ -16,5 +16,5 @@
#import "Source/common/SNTXPCBundleServiceInterface.h"
@interface SNTBundleService : NSObject<SNTBundleServiceXPC>
@interface SNTBundleService : NSObject <SNTBundleServiceXPC>
@end

View File

@@ -14,9 +14,9 @@
#import "Source/santabundleservice/SNTBundleService.h"
#include <stdatomic.h>
#import <CommonCrypto/CommonDigest.h>
#import <pthread/pthread.h>
#include <stdatomic.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -60,10 +60,9 @@
});
}
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
reply:(SNTBundleHashBlock)reply {
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply {
NSProgress *progress =
[NSProgress currentProgress] ? [NSProgress progressWithTotalUnitCount:100] : nil;
[NSProgress currentProgress] ? [NSProgress progressWithTotalUnitCount:100] : nil;
NSDate *startTime = [NSDate date];
@@ -92,7 +91,7 @@
// For most apps this should be "Contents/MacOS/AppName"
if (b.bundle.executablePath.length > b.bundlePath.length) {
event.fileBundleExecutableRelPath =
[b.bundle.executablePath substringFromIndex:b.bundlePath.length + 1];
[b.bundle.executablePath substringFromIndex:b.bundlePath.length + 1];
}
NSDictionary *relatedEvents = [self findRelatedBinaries:event progress:progress];
@@ -171,7 +170,7 @@
NSString *subpath = subpaths[i];
NSString *file =
[event.fileBundlePath stringByAppendingPathComponent:subpath].stringByStandardizingPath;
[event.fileBundlePath stringByAppendingPathComponent:subpath].stringByStandardizingPath;
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithResolvedPath:file error:NULL];
if (!fi.isExecutable) return;
@@ -269,17 +268,15 @@
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
NSString *sha256 = [[NSString alloc] initWithFormat:SHA256FormatString,
digest[0], digest[1], digest[2], digest[3],
digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19],
digest[20], digest[21], digest[22], digest[23],
digest[24], digest[25], digest[26], digest[27],
digest[28], digest[29], digest[30], digest[31]];
NSString *sha256 = [[NSString alloc]
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15], digest[16], digest[17],
digest[18], digest[19], digest[20], digest[21], digest[22], digest[23],
digest[24], digest[25], digest[26], digest[27], digest[28], digest[29],
digest[30], digest[31]];
p.completedUnitCount++;
[progress resignCurrent];

View File

@@ -20,15 +20,14 @@
#import "Source/common/SNTXPCBundleServiceInterface.h"
#import "Source/santabundleservice/SNTBundleService.h"
int main(int argc, const char *argv[]) {
@autoreleasepool {
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);
MOLXPCConnection *c =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCBundleServiceInterface serviceID]];
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCBundleServiceInterface serviceID]];
c.privilegedInterface = c.unprivilegedInterface =
[SNTXPCBundleServiceInterface bundleServiceInterface];
[SNTXPCBundleServiceInterface bundleServiceInterface];
c.exportedObject = [[SNTBundleService alloc] init];
[c resume];
[[NSRunLoop mainRunLoop] run];

View File

@@ -1,8 +1,10 @@
licenses(["notice"]) # Apache 2.0
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
load("//:helper.bzl", "santa_unit_test")
licenses(["notice"])
package(default_visibility = ["//:santa_package_group"])
objc_library(
name = "santactl_lib",
srcs = [
@@ -15,6 +17,8 @@ objc_library(
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandVersion.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetrics.m",
"Commands/sync/NSData+Zlib.h",
"Commands/sync/NSData+Zlib.m",
"Commands/sync/SNTCommandSync.m",
@@ -46,6 +50,7 @@ objc_library(
sdk_dylibs = ["libz"],
sdk_frameworks = ["IOKit"],
deps = [
":FCM_lib",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
@@ -53,6 +58,7 @@ objc_library(
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
@@ -64,18 +70,35 @@ objc_library(
"@FMDB",
"@MOLAuthenticatingURLSession",
"@MOLCodesignChecker",
"@MOLFCMClient",
"@MOLXPCConnection",
],
)
objc_library(
name = "FCM_lib",
srcs = ["Commands/sync/SNTCommandSyncFCM.m"],
hdrs = ["Commands/sync/SNTCommandSyncFCM.h"],
sdk_frameworks = ["SystemConfiguration"],
deps = [
"@MOLAuthenticatingURLSession",
],
)
macos_command_line_application(
name = "santactl",
bundle_id = "com.google.santa.ctl",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//visibility:public"],
deps = [":santactl_lib"],
)
@@ -123,13 +146,19 @@ santa_unit_test(
"Commands/sync/SNTCommandSyncStage.m",
"Commands/sync/SNTCommandSyncState.h",
"Commands/sync/SNTCommandSyncState.m",
"Commands/sync/SNTCommandSyncTest.m",
"SNTCommand.h",
"SNTCommand.m",
"SNTCommandController.h",
"SNTCommandController.m",
],
resources = glob([
"Commands/sync/testdata/*.json",
"Commands/sync/testdata/*.plist",
]),
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
@@ -140,8 +169,29 @@ santa_unit_test(
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLFCMClient",
"@MOLXPCConnection",
"@OCMock",
],
)
santa_unit_test(
name = "SNTCommandMetricsTest",
srcs = ["Commands/SNTCommandMetricsTest.m"],
structured_resources = glob(["Commands/testdata/*"]),
visibility = ["//:santa_package_group"],
deps = [
":santactl_lib",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
"@OCMock",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTCommandFileInfoTest",
":SNTCommandMetricsTest",
":SNTCommandSyncTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -23,7 +23,7 @@
#ifdef DEBUG
@interface SNTCommandBundleInfo : SNTCommand<SNTCommandProtocol>
@interface SNTCommandBundleInfo : SNTCommand <SNTCommandProtocol>
@end
@implementation SNTCommandBundleInfo
@@ -63,21 +63,22 @@ REGISTER_COMMAND_NAME(@"bundleinfo")
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
[bc resume];
[[bc remoteObjectProxy] hashBundleBinariesForEvent:se
reply:^(NSString *hash,
NSArray<SNTStoredEvent *> *events,
NSNumber *time) {
printf("Hashing time: %llu ms\n", time.unsignedLongLongValue);
printf("%lu events found\n", events.count);
printf("BundleHash: %s\n", hash.UTF8String);
[[bc remoteObjectProxy]
hashBundleBinariesForEvent:se
reply:^(NSString *hash, NSArray<SNTStoredEvent *> *events,
NSNumber *time) {
printf("Hashing time: %llu ms\n", time.unsignedLongLongValue);
printf("%lu events found\n", events.count);
printf("BundleHash: %s\n", hash.UTF8String);
for (SNTStoredEvent *event in events) {
printf("BundleID: %s \n\tSHA-256: %s \n\tPath: %s\n",
event.fileBundleID.UTF8String, event.fileSHA256.UTF8String, event.filePath.UTF8String);
}
[[bc remoteObjectProxy] spindown];
exit(0);
}];
for (SNTStoredEvent *event in events) {
printf("BundleID: %s \n\tSHA-256: %s \n\tPath: %s\n",
event.fileBundleID.UTF8String, event.fileSHA256.UTF8String,
event.filePath.UTF8String);
}
[[bc remoteObjectProxy] spindown];
exit(0);
}];
}
@end

View File

@@ -22,7 +22,7 @@
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@interface SNTCommandCacheHistogram : SNTCommand<SNTCommandProtocol>
@interface SNTCommandCacheHistogram : SNTCommand <SNTCommandProtocol>
@end
@implementation SNTCommandCacheHistogram
@@ -50,7 +50,7 @@ REGISTER_COMMAND_NAME(@"cachehistogram")
- (void)runWithArguments:(NSArray *)arguments {
[[self.daemonConn remoteObjectProxy] cacheBucketCount:^(NSArray *counts) {
NSMutableDictionary<NSNumber *, NSNumber *> *d = [NSMutableDictionary dictionary];
[counts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[counts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
d[obj] = @([d[obj] intValue] + 1);
}];
printf("There are %llu empty buckets\n", [d[@0] unsignedLongLongValue]);

View File

@@ -22,10 +22,9 @@
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
#ifdef DEBUG
@interface SNTCommandCheckCache : SNTCommand<SNTCommandProtocol>
@interface SNTCommandCheckCache : SNTCommand <SNTCommandProtocol>
@end
@implementation SNTCommandCheckCache
@@ -51,25 +50,26 @@ REGISTER_COMMAND_NAME(@"checkcache")
- (void)runWithArguments:(NSArray *)arguments {
santa_vnode_id_t vnodeID = [self vnodeIDForFile:arguments.firstObject];
[[self.daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
LOGI(@"File exists in [whitelist] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_DENY) {
LOGI(@"File exists in [blacklist] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_ALLOW_COMPILER) {
LOGI(@"File exists in [whitelist compiler] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE) {
LOGI(@"File exists in [whitelist pending_transitive] kernel cache");
exit(0);
} else if (action == ACTION_UNSET) {
LOGE(@"File does not exist in cache");
exit(1);
}
}];
[[self.daemonConn remoteObjectProxy]
checkCacheForVnodeID:vnodeID
withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
LOGI(@"File exists in [allowlist] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_DENY) {
LOGI(@"File exists in [blocklist] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_ALLOW_COMPILER) {
LOGI(@"File exists in [allowlist compiler] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE) {
LOGI(@"File exists in [allowlist pending_transitive] kernel cache");
exit(0);
} else if (action == ACTION_UNSET) {
LOGE(@"File does not exist in cache");
exit(1);
}
}];
}
- (santa_vnode_id_t)vnodeIDForFile:(NSString *)path {

View File

@@ -41,6 +41,7 @@ static NSString *const kCodeSigned = @"Code-signed";
static NSString *const kRule = @"Rule";
static NSString *const kSigningChain = @"Signing Chain";
static NSString *const kUniversalSigningChain = @"Universal Signing Chain";
static NSString *const kTeamID = @"Team ID";
// signing chain keys
static NSString *const kCommonName = @"Common Name";
@@ -65,7 +66,7 @@ NSString *formattedStringForKeyArray(NSArray<NSString *> *array) {
return result;
}
@interface SNTCommandFileInfo : SNTCommand<SNTCommandProtocol>
@interface SNTCommandFileInfo : SNTCommand <SNTCommandProtocol>
// Properties set from commandline flags
@property(nonatomic) BOOL recursive;
@@ -109,6 +110,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadURL;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadTimestamp;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadAgent;
@property(readonly, copy, nonatomic) SNTAttributeBlock teamID;
@property(readonly, copy, nonatomic) SNTAttributeBlock type;
@property(readonly, copy, nonatomic) SNTAttributeBlock pageZero;
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
@@ -144,50 +146,53 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
+ (NSString *)longHelpText {
return [NSString stringWithFormat:
@"The details provided will be the same ones Santa uses to make a decision\n"
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
@"the type of file."
@"\n"
@"Usage: santactl fileinfo [options] [file-paths]\n"
@" --recursive (-r): Search directories recursively.\n"
@" --json: Output in JSON format.\n"
@" --key: Search and return this one piece of information.\n"
@" You may specify multiple keys by repeating this flag.\n"
@" Valid Keys:\n"
@"%@\n"
@" Valid keys when using --cert-index:\n"
@"%@\n"
@" --cert-index: Supply an integer corresponding to a certificate of the\n"
@" signing chain to show info only for that certificate.\n"
@" 1 for the leaf certificate\n"
@" -1 for the root certificate\n"
@" 2 and up for the intermediates / root\n"
@"\n"
@" --filter: Use predicates of the form 'key=regex' to filter out which files\n"
@" are displayed. Valid keys are the same as for --key. Value is a\n"
@" case-insensitive regular expression which must match anywhere in\n"
@" the keyed property value for the file's info to be displayed.\n"
@" You may specify multiple filters by repeating this flag.\n"
@"\n"
@"Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes\n"
@" santactl fileinfo --key SHA-256 --json /usr/bin/yes\n"
@" santactl fileinfo /usr/bin/yes /bin/*\n"
@" santactl fileinfo /usr/bin -r --key Path --key SHA-256 --key Rule\n"
@" santactl fileinfo /usr/bin/* --filter Type=Script --filter Path=zip",
formattedStringForKeyArray(self.fileInfoKeys),
formattedStringForKeyArray(self.signingChainKeys)];
return
[NSString stringWithFormat:
@"The details provided will be the same ones Santa uses to make a decision\n"
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
@"the type of file."
@"\n"
@"Usage: santactl fileinfo [options] [file-paths]\n"
@" --recursive (-r): Search directories recursively.\n"
@" --json: Output in JSON format.\n"
@" --key: Search and return this one piece of information.\n"
@" You may specify multiple keys by repeating this flag.\n"
@" Valid Keys:\n"
@"%@\n"
@" Valid keys when using --cert-index:\n"
@"%@\n"
@" --cert-index: Supply an integer corresponding to a certificate of the\n"
@" signing chain to show info only for that certificate.\n"
@" 1 for the leaf certificate\n"
@" -1 for the root certificate\n"
@" 2 and up for the intermediates / root\n"
@"\n"
@" --filter: Use predicates of the form 'key=regex' to filter out which files\n"
@" are displayed. Valid keys are the same as for --key. Value is a\n"
@" case-insensitive regular expression which must match anywhere in\n"
@" the keyed property value for the file's info to be displayed.\n"
@" You may specify multiple filters by repeating this flag.\n"
@"\n"
@"Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes\n"
@" santactl fileinfo --key SHA-256 --json /usr/bin/yes\n"
@" santactl fileinfo /usr/bin/yes /bin/*\n"
@" santactl fileinfo /usr/bin -r --key Path --key SHA-256 --key Rule\n"
@" santactl fileinfo /usr/bin/* --filter Type=Script --filter Path=zip",
formattedStringForKeyArray(self.fileInfoKeys),
formattedStringForKeyArray(self.signingChainKeys)];
}
+ (NSArray<NSString *> *)fileInfoKeys {
return @[ kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr,
kDownloadReferrerURL, kDownloadURL, kDownloadTimestamp, kDownloadAgent,
kType, kPageZero, kCodeSigned, kRule, kSigningChain, kUniversalSigningChain ];
return @[
kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr, kDownloadReferrerURL,
kDownloadURL, kDownloadTimestamp, kDownloadAgent, kTeamID, kType, kPageZero, kCodeSigned, kRule,
kSigningChain, kUniversalSigningChain
];
}
+ (NSArray<NSString *> *)signingChainKeys {
return @[ kSHA256, kSHA1, kCommonName, kOrganization, kOrganizationalUnit, kValidFrom,
kValidUntil ];
return
@[ kSHA256, kSHA1, kCommonName, kOrganization, kOrganizationalUnit, kValidFrom, kValidUntil ];
}
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn {
@@ -196,22 +201,25 @@ REGISTER_COMMAND_NAME(@"fileinfo")
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
_propertyMap = @{ kPath : self.path,
kSHA256 : self.sha256,
kSHA1 : self.sha1,
kBundleName : self.bundleName,
kBundleVersion : self.bundleVersion,
kBundleVersionStr : self.bundleVersionStr,
kDownloadReferrerURL : self.downloadReferrerURL,
kDownloadURL : self.downloadURL,
kDownloadTimestamp : self.downloadTimestamp,
kDownloadAgent : self.downloadAgent,
kType : self.type,
kPageZero : self.pageZero,
kCodeSigned : self.codeSigned,
kRule : self.rule,
kSigningChain : self.signingChain,
kUniversalSigningChain : self.universalSigningChain };
_propertyMap = @{
kPath : self.path,
kSHA256 : self.sha256,
kSHA1 : self.sha1,
kBundleName : self.bundleName,
kBundleVersion : self.bundleVersion,
kBundleVersionStr : self.bundleVersionStr,
kDownloadReferrerURL : self.downloadReferrerURL,
kDownloadURL : self.downloadURL,
kDownloadTimestamp : self.downloadTimestamp,
kDownloadAgent : self.downloadAgent,
kType : self.type,
kPageZero : self.pageZero,
kCodeSigned : self.codeSigned,
kRule : self.rule,
kSigningChain : self.signingChain,
kUniversalSigningChain : self.universalSigningChain,
kTeamID : self.teamID,
};
_printQueue = dispatch_queue_create("com.google.santactl.print_queue", DISPATCH_QUEUE_SERIAL);
}
@@ -221,78 +229,78 @@ REGISTER_COMMAND_NAME(@"fileinfo")
#pragma mark property getters
- (SNTAttributeBlock)path {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.path;
};
}
- (SNTAttributeBlock)sha256 {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.SHA256;
};
}
- (SNTAttributeBlock)sha1 {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.SHA1;
};
}
- (SNTAttributeBlock)bundleName {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.bundleName;
};
}
- (SNTAttributeBlock)bundleVersion {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.bundleVersion;
};
}
- (SNTAttributeBlock)bundleVersionStr {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.bundleShortVersionString;
};
}
- (SNTAttributeBlock)downloadReferrerURL {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.quarantineRefererURL;
};
}
- (SNTAttributeBlock)downloadURL {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.quarantineDataURL;
};
}
- (SNTAttributeBlock)downloadTimestamp {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return [cmd.dateFormatter stringFromDate:fileInfo.quarantineTimestamp];
};
}
- (SNTAttributeBlock)downloadAgent {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return fileInfo.quarantineAgentBundleID;
};
}
- (SNTAttributeBlock)type {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
NSArray *archs = [fileInfo architectures];
if (archs.count == 0) {
return [fileInfo humanReadableFileType];
}
return [NSString stringWithFormat:@"%@ (%@)",
[fileInfo humanReadableFileType], [archs componentsJoinedByString:@", "]];
return [NSString stringWithFormat:@"%@ (%@)", [fileInfo humanReadableFileType],
[archs componentsJoinedByString:@", "]];
};
}
- (SNTAttributeBlock)pageZero {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
if ([fileInfo isMissingPageZero]) {
return @"__PAGEZERO segment missing/bad!";
}
@@ -301,37 +309,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
- (SNTAttributeBlock)codeSigned {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
NSError *error;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&error];
if (error) {
switch (error.code) {
case errSecCSUnsigned:
return @"No";
case errSecCSUnsigned: return @"No";
case errSecCSSignatureFailed:
case errSecCSStaticCodeChanged:
case errSecCSSignatureNotVerifiable:
case errSecCSSignatureUnsupported:
return @"Yes, but code/signature changed/unverifiable";
case errSecCSSignatureUnsupported: return @"Yes, but code/signature changed/unverifiable";
case errSecCSResourceDirectoryFailed:
case errSecCSResourceNotSupported:
case errSecCSResourceRulesInvalid:
case errSecCSResourcesInvalid:
case errSecCSResourcesNotFound:
case errSecCSResourcesNotSealed:
return @"Yes, but resources invalid";
case errSecCSResourcesNotSealed: return @"Yes, but resources invalid";
case errSecCSReqFailed:
case errSecCSReqInvalid:
case errSecCSReqUnsupported:
return @"Yes, but failed requirement validation";
case errSecCSInfoPlistFailed:
return @"Yes, but can't validate as Info.plist is missing";
case errSecCSReqUnsupported: return @"Yes, but failed requirement validation";
case errSecCSInfoPlistFailed: return @"Yes, but can't validate as Info.plist is missing";
case errSecCSSignatureInvalid:
if ([error.domain isEqualToString:@"com.google.molcodesignchecker"]) {
return @"Yes, but signing is not consistent for all architectures";
}
case CSSMERR_TP_CERT_REVOKED:
return @"Yes, but the signing certificate was revoked";
case CSSMERR_TP_CERT_REVOKED: return @"Yes, but the signing certificate was revoked";
default: {
return [NSString stringWithFormat:@"Yes, but failed to validate (%ld)", error.code];
}
@@ -345,54 +347,47 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
- (SNTAttributeBlock)rule {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
// If we previously were unable to connect, don't try again.
if (cmd.daemonUnavailable) return kCommunicationErrorMsg;
static dispatch_once_t token;
dispatch_once(&token, ^{ [cmd.daemonConn resume]; });
dispatch_once(&token, ^{
[cmd.daemonConn resume];
});
__block SNTEventState state;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSError *err;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&err];
[[cmd.daemonConn remoteObjectProxy] decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
[[cmd.daemonConn remoteObjectProxy]
decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
teamID:[csc.signingInformation valueForKey:@"teamid"]
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
cmd.daemonUnavailable = YES;
return kCommunicationErrorMsg;
} else {
NSMutableString *output =
(SNTEventStateAllow & state) ? @"Whitelisted".mutableCopy : @"Blacklisted".mutableCopy;
(SNTEventStateAllow & state) ? @"Allowed".mutableCopy : @"Blocked".mutableCopy;
switch (state) {
case SNTEventStateAllowUnknown:
case SNTEventStateBlockUnknown:
[output appendString:@" (Unknown)"];
break;
case SNTEventStateBlockUnknown: [output appendString:@" (Unknown)"]; break;
case SNTEventStateAllowBinary:
case SNTEventStateBlockBinary:
[output appendString:@" (Binary)"];
break;
case SNTEventStateBlockBinary: [output appendString:@" (Binary)"]; break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate:
[output appendString:@" (Certificate)"];
break;
case SNTEventStateBlockCertificate: [output appendString:@" (Certificate)"]; break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID: [output appendString:@" (TeamID)"]; break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope:
[output appendString:@" (Scope)"];
break;
case SNTEventStateAllowCompiler:
[output appendString:@" (Compiler)"];
break;
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
default:
output = @"None".mutableCopy;
break;
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
case SNTEventStateAllowCompiler: [output appendString:@" (Compiler)"]; break;
case SNTEventStateAllowTransitive: [output appendString:@" (Transitive)"]; break;
default: output = @"None".mutableCopy; break;
}
if (cmd.prettyOutput) {
if ((SNTEventStateAllow & state)) {
@@ -412,7 +407,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
- (SNTAttributeBlock)signingChain {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
if (!csc.certificates.count) return nil;
NSMutableArray *certs = [[NSMutableArray alloc] initWithCapacity:csc.certificates.count];
@@ -432,22 +427,22 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
- (SNTAttributeBlock)universalSigningChain {
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
if (csc.certificates.count) return nil;
if (!csc.universalSigningInformation) return nil;
NSMutableArray *universal = [NSMutableArray array];
for (NSDictionary *arch in csc.universalSigningInformation) {
[universal addObject:@{ @"arch" : arch.allKeys.firstObject }];
[universal addObject:@{@"arch" : arch.allKeys.firstObject}];
int flags = [arch.allValues.firstObject[(__bridge id)kSecCodeInfoFlags] intValue];
if (flags & kSecCodeSignatureAdhoc) {
[universal addObject:@{ @"ad-hoc" : @YES }];
[universal addObject:@{@"ad-hoc" : @YES}];
continue;
}
NSArray *certs = arch.allValues.firstObject[(__bridge id)kSecCodeInfoCertificates];
NSArray *chain = [MOLCertificate certificatesFromArray:certs];
if (!chain.count) {
[universal addObject:@{ @"unsigned" : @YES }];
[universal addObject:@{@"unsigned" : @YES}];
continue;
}
for (MOLCertificate *c in chain) {
@@ -471,7 +466,14 @@ REGISTER_COMMAND_NAME(@"fileinfo")
};
}
# pragma mark -
- (SNTAttributeBlock)teamID {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
return [csc.signingInformation valueForKey:@"teamid"];
};
}
#pragma mark -
// Entry point for the command.
- (void)runWithArguments:(NSArray *)arguments {
@@ -505,12 +507,12 @@ REGISTER_COMMAND_NAME(@"fileinfo")
[filePaths enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^(NSString *path, NSUInteger idx, BOOL *stop) {
NSString *fullPath = [path stringByStandardizingPath];
if (path.length && [path characterAtIndex:0] != '/') {
fullPath = [cwd stringByAppendingPathComponent:fullPath];
}
[self recurseAtPath:fullPath];
}];
NSString *fullPath = [path stringByStandardizingPath];
if (path.length && [path characterAtIndex:0] != '/') {
fullPath = [cwd stringByAppendingPathComponent:fullPath];
}
[self recurseAtPath:fullPath];
}];
// Wait for all tasks in print queue to complete.
dispatch_group_wait(self.printGroup, DISPATCH_TIME_FOREVER);
@@ -563,7 +565,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
} else if (isDir && !isBundle) {
dispatch_group_async(self.printGroup, self.printQueue, ^{
fprintf(stderr, "%s is a directory. Use the -r flag to search recursively.\n",
[path UTF8String]);
[path UTF8String]);
});
} else {
[operationQueue addOperationWithBlock:^{
@@ -646,8 +648,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
// a) do we want JSON output?
// b) is there only one key?
// c) are we displaying a cert?
BOOL singleKey = (self.outputKeyList.count == 1 &&
![self.outputKeyList.firstObject isEqual:kSigningChain]);
BOOL singleKey =
(self.outputKeyList.count == 1 && ![self.outputKeyList.firstObject isEqual:kSigningChain]);
NSMutableString *output = [NSMutableString string];
if (self.jsonOutput) {
[output appendString:[self jsonStringForDictionary:outputDict]];
@@ -660,8 +662,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
if (singleKey) {
[output appendFormat:@"%@\n", outputDict[key]];
} else {
[output appendFormat:@"%-*s: %@\n",
(int)self.maxKeyWidth, key.UTF8String, outputDict[key]];
[output
appendFormat:@"%-*s: %@\n", (int)self.maxKeyWidth, key.UTF8String, outputDict[key]];
}
}
}
@@ -701,9 +703,11 @@ REGISTER_COMMAND_NAME(@"fileinfo")
int index = 0;
NSScanner *scanner = [NSScanner scannerWithString:arguments[i]];
if (![scanner scanInt:&index] || !scanner.atEnd || index == 0 || index < -1) {
[self printErrorUsageAndExit:[NSString stringWithFormat:
@"\n\"%@\" is an invalid argument for --cert-index\n"
@" --cert-index argument must be one of -1, 1, 2, 3, ...", arguments[i]]];
[self
printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid argument for --cert-index\n"
@" --cert-index argument must be one of -1, 1, 2, 3, ...",
arguments[i]]];
}
self.certIndex = index;
} else if ([arg caseInsensitiveCompare:@"--key"] == NSOrderedSame) {
@@ -721,22 +725,24 @@ REGISTER_COMMAND_NAME(@"fileinfo")
NSRange range = [arguments[i] rangeOfString:@"="];
if (range.location == NSNotFound || range.location == 0 ||
range.location == arguments[i].length - 1) {
[self printErrorUsageAndExit:[NSString stringWithFormat:
@"\n\"%@\" is an invalid filter predicate.\n"
@"Filter predicates must be of the form key=regex"
@" (with no spaces around \"=\")", arguments[i]]];
[self printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid filter predicate.\n"
@"Filter predicates must be of the form key=regex"
@" (with no spaces around \"=\")",
arguments[i]]];
}
NSString *key = [arguments[i] substringToIndex:range.location];
NSString *rhs = [arguments[i] substringFromIndex:range.location+1];
NSString *rhs = [arguments[i] substringFromIndex:range.location + 1];
// Convert right-hand side of '=' into a regular expression object.
NSError *error;
NSRegularExpression *regex =
[NSRegularExpression regularExpressionWithPattern:rhs
options:NSRegularExpressionCaseInsensitive
error:&error];
[NSRegularExpression regularExpressionWithPattern:rhs
options:NSRegularExpressionCaseInsensitive
error:&error];
if (error) {
[self printErrorUsageAndExit:[NSString stringWithFormat:
@"\n\"%@\" is an invalid regular expression in filter argument.\n", rhs]];
[self printErrorUsageAndExit:[NSString stringWithFormat:@"\n\"%@\" is an invalid regular "
@"expression in filter argument.\n",
rhs]];
}
filters[key] = regex;
} else if ([arg caseInsensitiveCompare:@"--recursive"] == NSOrderedSame ||
@@ -752,28 +758,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
NSArray *validKeys = [[self class] signingChainKeys];
for (NSString *key in keys) {
if (![validKeys containsObject:key]) {
[self printErrorUsageAndExit:
[self
printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid key when using --cert-index", key]];
}
}
for (NSString *key in filters) {
if (![validKeys containsObject:key]) {
[self printErrorUsageAndExit:[NSString stringWithFormat:
@"\n\"%@\" is an invalid filter key when using --cert-index", key]];
[self
printErrorUsageAndExit:
[NSString
stringWithFormat:@"\n\"%@\" is an invalid filter key when using --cert-index", key]];
}
}
} else {
NSArray *validKeys = [[self class] fileInfoKeys];
for (NSString *key in keys) {
if (![validKeys containsObject:key]) {
[self printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid key", key]];
[self
printErrorUsageAndExit:[NSString stringWithFormat:@"\n\"%@\" is an invalid key", key]];
}
}
for (NSString *key in filters) {
if (![validKeys containsObject:key] || [key isEqualToString:kSigningChain]) {
[self printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid filter key", key]];
[self printErrorUsageAndExit:[NSString
stringWithFormat:@"\n\"%@\" is an invalid filter key", key]];
}
}
}

View File

@@ -94,8 +94,10 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
}
- (void)testParseArgumentsFilePaths {
NSArray *args = @[ @"/usr/bin/yes", @"/bin/mv", @"--key", @"SHA-256", @"/bin/ls", @"--json",
@"/bin/rm", @"--cert-index", @"1", @"/bin/cp" ];
NSArray *args = @[
@"/usr/bin/yes", @"/bin/mv", @"--key", @"SHA-256", @"/bin/ls", @"--json", @"/bin/rm",
@"--cert-index", @"1", @"/bin/cp"
];
NSArray *filePaths = [self.cfi parseArguments:args];
XCTAssertEqual(filePaths.count, 5);
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
@@ -106,7 +108,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
}
- (void)testParseArgumentsFilePathSameAsKey {
NSArray *filePaths = [self.cfi parseArguments:@[ @"--key", @"Rule", @"Rule"]];
NSArray *filePaths = [self.cfi parseArguments:@[ @"--key", @"Rule", @"Rule" ]];
XCTAssertTrue([self.cfi.outputKeyList containsObject:@"Rule"]);
XCTAssertEqual(filePaths.count, 1);
XCTAssertTrue([filePaths containsObject:@"Rule"]);
@@ -115,134 +117,136 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
- (void)testKeysAlignWithPropertyMap {
NSArray *mapKeys = self.cfi.propertyMap.allKeys;
NSArray *fileInfokeys = [SNTCommandFileInfo fileInfoKeys];
for (NSString *key in fileInfokeys) XCTAssertTrue([mapKeys containsObject:key]);
for (NSString *key in mapKeys) XCTAssertTrue([fileInfokeys containsObject:key]);
for (NSString *key in fileInfokeys)
XCTAssertTrue([mapKeys containsObject:key]);
for (NSString *key in mapKeys)
XCTAssertTrue([fileInfokeys containsObject:key]);
}
- (void)testCodeSignedNo {
NSError *err = [NSError errorWithDomain:@"" code:errSecCSUnsigned userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), @"No");
}
- (void)testCodeSignedSignatureFailed {
NSString *expected = @"Yes, but code/signature changed/unverifiable";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureFailed userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedStaticCodeChanged {
NSString *expected = @"Yes, but code/signature changed/unverifiable";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSStaticCodeChanged userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedSignatureNotVerifiable {
NSString *expected = @"Yes, but code/signature changed/unverifiable";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureNotVerifiable userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedSignatureUnsupported {
NSString *expected = @"Yes, but code/signature changed/unverifiable";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureUnsupported userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedResourceDirectoryFailed {
NSString *expected = @"Yes, but resources invalid";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceDirectoryFailed userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedResourceNotSupported {
NSString *expected = @"Yes, but resources invalid";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceNotSupported userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedResourceRulesInvalid {
NSString *expected = @"Yes, but resources invalid";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceRulesInvalid userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedResourcesInvalid {
NSString *expected = @"Yes, but resources invalid";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesInvalid userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedResourcesNotFound {
NSString *expected = @"Yes, but resources invalid";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotFound userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedResourcesNotSealed {
NSString *expected = @"Yes, but resources invalid";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotSealed userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedReqFailed {
NSString *expected = @"Yes, but failed requirement validation";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqFailed userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedReqInvalid {
NSString *expected = @"Yes, but failed requirement validation";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqInvalid userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedReqUnsupported {
NSString *expected = @"Yes, but failed requirement validation";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqUnsupported userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedInfoPlistFailed {
NSString *expected = @"Yes, but can't validate as Info.plist is missing";
NSError *err = [NSError errorWithDomain:@"" code:errSecCSInfoPlistFailed userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testCodeSignedDefault {
NSString *expected = @"Yes, but failed to validate (999)";
NSError *err = [NSError errorWithDomain:@"" code:999 userInfo:nil];
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
.andReturn(self.cscMock);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}

View File

@@ -22,7 +22,7 @@
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@interface SNTCommandFlushCache : SNTCommand<SNTCommandProtocol>
@interface SNTCommandFlushCache : SNTCommand <SNTCommandProtocol>
@end
@implementation SNTCommandFlushCache

View File

@@ -0,0 +1,22 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@interface SNTCommandMetrics : SNTCommand <SNTCommandProtocol>
- (void)prettyPrintMetrics:(NSDictionary *)metircs asJSON:(BOOL)exportJSON;
@end

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