Compare commits

...

80 Commits

Author SHA1 Message Date
Tom Burgin
b824a8e3e0 santad: only store events if there is a sync server configured (#721)
* santad: only store events if there is a sync server configured

* SNTExecutionControllerTest stub sync server

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

* wording update

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

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

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

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

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

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

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

* clang-format

* fix tests

* s/SNTCommandSync/SNTSync

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

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

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

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

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

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

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

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

View File

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

View File

@@ -9,7 +9,7 @@ on:
jobs:
preqs:
runs-on: macos-latest
runs-on: ubuntu-latest
outputs:
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
build_driver: ${{ steps.step1.outputs.build_driver }}
@@ -32,29 +32,42 @@ jobs:
for file in `cat files.txt`; do
if [[ $file = Source/* ]]; then
build_and_run_test=1;
if [[ $file = Source/santa_driver/* ]]; then
if [[ $file = Source/santa_driver/* || $file = Source/common/* ]]; then
build_driver=1;
break;
fi
fi
done
if [[ $build_and_run_test != 0 ]]; then
if [[ $build_and_run_test != 0 ]]; then
echo "NEED TO RUN BUILD AND TESTS"
echo "::set-output name=run_build_and_tests::true"
else
else
echo "::set-output name=run_build_and_tests::false"
fi
if [[ $build_driver != 0 ]]; then
echo "NEED TO BUILD DRIVER"
echo "::set-output name=build_driver::true"
else
else
echo "::set-output name=build_driver::false"
fi
lint:
runs-on: ubuntu-latest
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Run linters
run: ./Testing/lint.sh
build_userspace:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -63,7 +76,11 @@ jobs:
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
build_driver:
runs-on: macos-latest
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:
@@ -72,7 +89,11 @@ jobs:
run: bazel build --apple_generate_dsym -c opt :release_driver --define=SANTA_BUILD_TYPE=ci
unit_tests:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -81,7 +102,7 @@ jobs:
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci
test_coverage:
runs-on: macos-latest
runs-on: macos-11
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -94,3 +115,12 @@ jobs:
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

4
.gitignore vendored
View File

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

39
BUILD
View File

@@ -40,7 +40,6 @@ package_group(
packages = ["//..."],
)
################################################################################
# Loading/Unloading/Reloading
################################################################################
@@ -49,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
""",
@@ -59,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
""",
)
@@ -95,11 +96,13 @@ genrule(
"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",
],
@@ -120,9 +123,9 @@ genrule(
# Copy config files
for SRC in $(SRCS); do
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
if [[ "$$(dirname $${SRC})" == *"Conf"* ]]; then
mkdir -p $(@D)/conf
cp $${SRC} $(@D)/conf/
cp -H $${SRC} $(@D)/conf/
fi
done
@@ -141,6 +144,10 @@ 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
@@ -223,15 +230,17 @@ genrule(
test_suite(
name = "unit_tests",
tests = [
"//Source/common:SantaCacheTest",
"//Source/common:SNTFileInfoTest",
"//Source/common:SNTPrefixTreeTest",
"//Source/santactl:SNTCommandFileInfoTest",
"//Source/santactl:SNTCommandSyncTest",
"//Source/santad:SNTApplicationTest",
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
"//Source/santad:SNTRuleTableTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
"//Source/common:unit_tests",
"//Source/santactl:unit_tests",
"//Source/santad:unit_tests",
"//Source/santametricservice:unit_tests",
"//Source/santasyncservice:unit_tests",
],
)
test_suite(
name = "benchmarks",
tests = [
"//Source/santad:SNTApplicationBenchmark",
],
)

View File

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

@@ -24,6 +24,9 @@ mkdir -p /usr/local/bin
# Load com.google.santa.bundleservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load com.google.santa.metricservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
[[ -z "${GUI_USER}" ]] && 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

View File

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

View File

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

View File

@@ -1,2 +1,2 @@
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
/var/db/santa/santa.log root:wheel 644 10 25000 * NZ
/var/db/santa/santa.log root:wheel 644 10 25000 * Z

View File

@@ -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
@@ -43,6 +46,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
/bin/rm -rf /Applications/Santa.app 2>&1
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
/bin/rm /etc/asl/com.google.santa.asl.conf
# Copy new files.
/bin/mkdir -p /var/db/santa
@@ -58,8 +62,8 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/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.
@@ -71,6 +75,9 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Load com.google.santa.bundleservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load com.google.santa.metricservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load GUI agent if someone is logged in.
[[ -z "${GUI_USER}" ]] && exit 0

View File

@@ -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

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

View File

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

View File

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

View File

@@ -87,6 +87,10 @@ objc_library(
cc_library(
name = "SNTKernelCommon",
hdrs = ["SNTKernelCommon.h"],
defines = [
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
)
cc_library(
@@ -96,7 +100,11 @@ cc_library(
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = ["KERNEL"],
defines = [
"KERNEL",
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
)
objc_library(
@@ -123,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"],
)
@@ -201,6 +213,7 @@ objc_library(
name = "SNTMetricSet",
srcs = ["SNTMetricSet.m"],
hdrs = ["SNTMetricSet.h"],
deps = [":SNTCommonEnums"],
)
objc_library(
@@ -242,9 +255,9 @@ santa_unit_test(
name = "SNTFileInfoTest",
srcs = ["SNTFileInfoTest.m"],
resources = [
"testdata/32bitplist",
"testdata/bad_pagezero",
"testdata/missing_pagezero",
"testdata/32bitplist",
],
structured_resources = glob([
"testdata/BundleExample.app/**",
@@ -264,3 +277,14 @@ santa_unit_test(
srcs = ["SNTMetricSetTest.m"],
deps = [":SNTMetricSet"],
)
test_suite(
name = "unit_tests",
tests = [
":SNTFileInfoTest",
":SNTMetricSetTest",
":SNTPrefixTreeTest",
":SantaCacheTest",
],
visibility = ["//:santa_package_group"],
)

View File

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

View File

@@ -24,6 +24,7 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {
SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
SNTRuleTypeTeamID = 3,
};
typedef NS_ENUM(NSInteger, SNTRuleState) {
@@ -55,6 +56,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateBlockBinary = 1 << 17,
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
SNTEventStateBlockTeamID = 1 << 20,
// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
@@ -64,6 +66,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
SNTEventStateAllowTeamID = 1 << 31,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
@@ -94,7 +97,7 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeUnknown,
SNTMetricFormatTypeRawJSON,
SNTMetricFormatTypeJSON,
SNTMetricFormatTypeMonarchJSON,
};
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";

View File

@@ -185,6 +185,12 @@
#pragma mark - GUI Settings
///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
///
@property(readonly, nonatomic) NSString *aboutText;
///
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
/// If unset, the button will not be displayed.
@@ -248,6 +254,14 @@
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// Proxy settings for syncing.
/// This dictionary is passed directly to NSURLSession. The allowed keys
/// are loosely documented at
/// https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants.
///
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
///
/// The machine owner.
///
@@ -268,6 +282,23 @@
///
@property(nonatomic) BOOL syncCleanRequired;
///
/// USB Mount Blocking. Defaults to false.
///
@property(nonatomic) BOOL blockUSBMount;
///
/// Comma-seperated `$ mount -o` arguments used for forced remounting of USB devices. Default
/// to fully allow/deny without remounting if unset.
///
@property(nonatomic) NSArray<NSString *> *remountUSBMode;
///
/// When `blockUSBMount` is set, this is the message shown to the user when a device is blocked
/// If this message is not configured, a reasonable default is provided.
///
@property(readonly, nonatomic) NSString *usbBlockMessage;
///
/// If set, this over-rides the default machine ID used for syncing.
///
@@ -386,6 +417,15 @@
///
@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

@@ -45,6 +45,7 @@ static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The keys managed by a mobileconfig.
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kSyncProxyConfigKey = @"SyncProxyConfiguration";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
@@ -59,6 +60,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";
@@ -94,6 +96,8 @@ static NSString *const kFCMAPIKey = @"FCMAPIKey";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting";
static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
@@ -104,6 +108,8 @@ 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";
@@ -119,6 +125,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
Class string = [NSString class];
Class data = [NSData class];
Class array = [NSArray class];
Class dictionary = [NSDictionary class];
_syncServerKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveRulesKey : number,
@@ -127,6 +134,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
@@ -141,8 +150,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kAboutText : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
@@ -151,6 +163,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kSyncBaseURLKey : string,
kSyncProxyConfigKey : dictionary,
kClientAuthCertificateFileKey : string,
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
@@ -177,6 +190,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kFCMAPIKey : string,
kMetricFormat : string,
kMetricURL : string,
kMetricExportInterval : number,
kMetricExtraLabels : dictionary,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -256,6 +271,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingAboutText {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [self configStateSet];
}
@@ -384,6 +403,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBadSignatureProtection {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -471,6 +494,20 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return filters;
}
- (void)setRemountUSBMode:(NSArray<NSString *> *)args {
[self updateSyncStateForKey:kRemountUSBModeKey value:args];
}
- (NSArray<NSString *> *)remountUSBMode {
NSArray<NSString *> *args = self.configState[kRemountUSBModeKey];
for (id arg in args) {
if (![arg isKindOfClass:[NSString class]]) {
return nil;
}
}
return args;
}
- (NSURL *)syncBaseURL {
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
@@ -478,6 +515,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return url;
}
- (NSDictionary *)syncProxyConfig {
return self.configState[kSyncProxyConfigKey];
}
- (BOOL)enablePageZeroProtection {
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
return number ? [number boolValue] : YES;
@@ -488,6 +529,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]];
}
@@ -621,7 +666,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (BOOL)enableSysxCache {
NSNumber *number = self.configState[kEnableSysxCache];
return number ? [number boolValue] : NO;
return number ? [number boolValue] : YES;
}
- (BOOL)enableForkAndExitLogging {
@@ -660,6 +705,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
}
- (void)setBlockUSBMount:(BOOL)enabled {
[self updateSyncStateForKey:kBlockUSBMountKey value:@(enabled)];
}
- (BOOL)blockUSBMount {
NSNumber *number = self.configState[kBlockUSBMountKey];
return number ? [number boolValue] : NO;
}
///
/// Returns YES if all of the necessary options are set to export metrics, NO
/// otherwise.
@@ -671,12 +725,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (SNTMetricFormatType)metricFormat {
NSString *normalized = [self.configState[kMetricFormat] lowercaseString];
normalized = [normalized stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([normalized isEqualToString:@"rawjson"]) {
return SNTMetricFormatTypeRawJSON;
} else if ([normalized isEqualToString:@"json"]) {
return SNTMetricFormatTypeJSON;
} else if ([normalized isEqualToString:@"monarchjson"]) {
return SNTMetricFormatTypeMonarchJSON;
} else {
return SNTMetricFormatTypeUnknown;
}
@@ -686,6 +741,20 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [NSURL URLWithString:self.configState[kMetricURL]];
}
// Returns a default value of 30 (for 30 seconds).
- (NSUInteger)metricExportInterval {
NSNumber *configuredInterval = self.configState[kMetricExportInterval];
if (configuredInterval == nil) {
return 30;
}
return [configuredInterval unsignedIntegerValue];
}
- (NSDictionary *)extraMetricLabels {
return self.configState[kMetricExtraLabels];
}
#pragma mark Private
///

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -34,6 +34,7 @@
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply;
///
@@ -46,6 +47,8 @@
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
- (void)setAllowedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply;
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;

View File

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

View File

@@ -39,7 +39,7 @@
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive))reply;
int64_t transitive, int64_t teamID))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
///
@@ -57,6 +57,7 @@
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTEventState))reply;
///
@@ -71,6 +72,11 @@
- (void)enableBundles:(void (^)(BOOL))reply;
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
///
/// Metrics ops
///
- (void)metrics:(void (^)(NSDictionary *))reply;
///
/// GUI Ops
///

View File

@@ -48,19 +48,20 @@ 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",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
entitlements = "Santa.app.entitlements",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
entitlements = "Santa.app.entitlements",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",

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>

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

@@ -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

@@ -32,10 +32,10 @@ cc_library(
"SANTA_VERSION=" + SANTA_VERSION,
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLoggingKernel",
"//Source/common:SNTPrefixTreeKernel",
"//Source/common:SantaCache",
],
alwayslink = 1,
)

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,145 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/SNTCommandMetrics.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@implementation SNTCommandMetrics
REGISTER_COMMAND_NAME(@"metrics")
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
}
+ (NSString *)shortHelpText {
return @"Show Santa metric information.";
}
+ (NSString *)longHelpText {
return (@"Provides metrics about Santa's operation while it's running.\n"
@" Use --json to output in JSON format");
}
- (void)prettyPrintRootLabels:(NSDictionary *)rootLabels {
for (NSString *label in rootLabels) {
const char *labelStr = [label cStringUsingEncoding:NSUTF8StringEncoding];
const char *valueStr = [rootLabels[label] cStringUsingEncoding:NSUTF8StringEncoding];
printf(" %-25s | %s\n", labelStr, valueStr);
}
}
- (void)prettyPrintMetricValues:(NSDictionary *)metrics {
for (NSString *metricName in metrics) {
NSDictionary *metric = metrics[metricName];
const char *metricNameStr = [metricName UTF8String];
const char *description = [metric[@"description"] UTF8String];
NSString *metricType = SNTMetricMakeStringFromMetricType([metric[@"type"] integerValue]);
const char *metricTypeStr = [metricType UTF8String];
printf(" %-25s | %s\n", "Metric Name", metricNameStr);
printf(" %-25s | %s\n", "Description", description);
printf(" %-25s | %s\n", "Type", metricTypeStr);
for (NSString *fieldName in metric[@"fields"]) {
for (NSDictionary *field in metric[@"fields"][fieldName]) {
const char *fieldNameStr = [fieldName cStringUsingEncoding:NSUTF8StringEncoding];
const char *fieldValueStr = [field[@"value"] cStringUsingEncoding:NSUTF8StringEncoding];
const char *createdStr = [field[@"created"] UTF8String];
const char *lastUpdatedStr = [field[@"last_updated"] UTF8String];
const char *data = [[NSString stringWithFormat:@"%@", field[@"data"]] UTF8String];
if (strlen(fieldNameStr) > 0) {
printf(" %-25s | %s=%s\n", "Field", fieldNameStr, fieldValueStr);
}
printf(" %-25s | %s\n", "Created", createdStr);
printf(" %-25s | %s\n", "Last Updated", lastUpdatedStr);
printf(" %-25s | %s\n", "Data", data);
}
}
printf("\n");
}
}
- (void)prettyPrintMetrics:(NSDictionary *)metrics asJSON:(BOOL)exportJSON {
BOOL exportMetrics = [[SNTConfigurator configurator] exportMetrics];
NSURL *metricsURLStr = [[SNTConfigurator configurator] metricURL];
SNTMetricFormatType metricFormat = [[SNTConfigurator configurator] metricFormat];
NSUInteger metricExportInterval = [[SNTConfigurator configurator] metricExportInterval];
NSDictionary *normalizedMetrics = SNTMetricConvertDatesToISO8601Strings(metrics);
if (exportJSON) {
// Format
NSData *metricData = [NSJSONSerialization dataWithJSONObject:normalizedMetrics
options:NSJSONWritingPrettyPrinted
error:nil];
NSString *metricStr = [[NSString alloc] initWithData:metricData encoding:NSUTF8StringEncoding];
printf("%s\n", [metricStr UTF8String]);
return;
}
if (!exportMetrics) {
printf("Metrics not configured\n");
return;
}
printf(">>> Metrics Info\n");
printf(" %-25s | %s\n", "Metrics Server", [metricsURLStr.absoluteString UTF8String]);
printf(" %-25s | %s\n", "Metrics Format",
[SNTMetricStringFromMetricFormatType(metricFormat) UTF8String]);
printf(" %-25s | %lu\n", "Export Interval (seconds)", metricExportInterval);
printf("\n");
printf(">>> Root Labels\n");
[self prettyPrintRootLabels:normalizedMetrics[@"root_labels"]];
printf("\n");
printf(">>> Metrics \n");
[self prettyPrintMetricValues:normalizedMetrics[@"metrics"]];
}
- (void)runWithArguments:(NSArray *)arguments {
__block NSDictionary *metrics;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] metrics:^(NSDictionary *exportedMetrics) {
metrics = exportedMetrics;
dispatch_group_leave(group);
}];
// Wait a maximum of 5s for metrics collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
}
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];
exit(0);
}
@end

View File

@@ -0,0 +1,134 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/santactl/Commands/SNTCommandMetrics.h"
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
@interface SNTCommandMetricsTest : XCTestCase
@property NSString *tempDir;
@property id mockConfigurator;
@end
@implementation SNTCommandMetricsTest
- (void)setUp {
// create a temp dir
char template[] = "/tmp/sntcommandmetrictest.XXXXXXX";
char *tempPath = mkdtemp(template);
if (tempPath == NULL) {
NSLog(@"Unable to make temp directory");
exit(1);
}
self.tempDir =
[[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempPath
length:strlen(tempPath)];
// mock the SNTConfigurator
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator exportMetrics]).andReturn(YES);
OCMStub([self.mockConfigurator metricFormat]).andReturn(SNTMetricFormatTypeMonarchJSON);
OCMStub([self.mockConfigurator metricURL])
.andReturn([NSURL URLWithString:@"http://localhost:2444/submit"]);
OCMStub([self.mockConfigurator metricExportInterval]).andReturn((NSUInteger)30);
}
- (void)tearDown {
// delete the temp dir
NSError *err;
[[NSFileManager defaultManager] removeItemAtPath:self.tempDir error:&err];
if (err != nil) {
NSLog(@"unable to remove %@, error: %@", self.tempDir, err);
}
dup2(1, STDOUT_FILENO);
}
- (void)testPrettyPrintingJSON {
NSError *err;
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"Commands/testdata/metrics-prettyprint.json"];
NSString *goldenFileContents = [[NSString alloc]
initWithData:[NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&err]
encoding:NSUTF8StringEncoding];
XCTAssertNil(err, @"failed to read golden file %@ for testPrettyPrintingJSON", path);
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSString *outputPath = [NSString pathWithComponents:@[ self.tempDir, @"test.data" ]];
// redirect stdout
int fd = open([outputPath UTF8String], O_TRUNC | O_WRONLY | O_CREAT, 0600);
int saved_stdout = dup(fileno(stdout));
dup2(fd, fileno(stdout));
[metricsCmd prettyPrintMetrics:[SNTMetricFormatTestHelper createValidMetricsDictionary]
asJSON:YES];
// restore stdout
fflush(stdout);
dup2(saved_stdout, fileno(stdout));
// open test file assert equal with golden file
NSString *commandOutput =
[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:outputPath]
encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(goldenFileContents, commandOutput,
@"Metrics command command did not produce expected output");
}
- (void)testPrettyPrinting {
NSError *err;
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"Commands/testdata/metrics-prettyprint.txt"];
NSString *goldenFileContents = [[NSString alloc]
initWithData:[NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&err]
encoding:NSUTF8StringEncoding];
XCTAssertNil(err, @"failed to read golden file %@ for testPrettyPrinting", path);
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSString *outputPath = [NSString pathWithComponents:@[ self.tempDir, @"test.data" ]];
// redirect stdout
int fd = open([outputPath UTF8String], O_TRUNC | O_WRONLY | O_CREAT, 0600);
int saved_stdout = dup(fileno(stdout));
dup2(fd, fileno(stdout));
[metricsCmd prettyPrintMetrics:[SNTMetricFormatTestHelper createValidMetricsDictionary]
asJSON:NO];
// restore stdout
fflush(stdout);
dup2(saved_stdout, fileno(stdout));
// open test file assert equal with golden file
NSString *commandOutput =
[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:outputPath]
encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(goldenFileContents, commandOutput,
@"Metrics command command did not produce expected output");
}
@end

View File

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

View File

@@ -94,17 +94,19 @@ REGISTER_COMMAND_NAME(@"status")
}
// Database counts
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1, teamIDRuleCount = -1;
__block int64_t compilerRuleCount = -1, transitiveRuleCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate,
int64_t compiler, int64_t transitive) {
binaryRuleCount = binary;
certRuleCount = certificate;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
[[self.daemonConn remoteObjectProxy]
databaseRuleCounts:^(int64_t binary, int64_t certificate, int64_t compiler, int64_t transitive,
int64_t teamID) {
binaryRuleCount = binary;
certRuleCount = certificate;
teamIDRuleCount = teamID;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseEventCount:^(int64_t count) {
eventCount = count;
@@ -172,6 +174,10 @@ REGISTER_COMMAND_NAME(@"status")
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
BOOL exportMetrics = [[SNTConfigurator configurator] exportMetrics];
NSURL *metricsURLStr = [[SNTConfigurator configurator] metricURL];
NSUInteger metricExportInterval = [[SNTConfigurator configurator] metricExportInterval];
if ([arguments containsObject:@"--json"]) {
NSMutableDictionary *stats = [@{
@"daemon" : @{
@@ -226,6 +232,7 @@ REGISTER_COMMAND_NAME(@"status")
printf(">>> Database Info\n");
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %lld\n", "TeamID Rules", teamIDRuleCount);
printf(" %-25s | %lld\n", "Compiler Rules", compilerRuleCount);
printf(" %-25s | %lld\n", "Transitive Rules", transitiveRuleCount);
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
@@ -241,6 +248,12 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %s\n", "Bundle Scanning", (enableBundles ? "Yes" : "No"));
printf(" %-25s | %s\n", "Transitive Rules", (enableTransitiveRules ? "Yes" : "No"));
}
if (exportMetrics) {
printf(">>> Metrics Info\n");
printf(" %-25s | %s\n", "Metrics Server", [[metricsURLStr absoluteString] UTF8String]);
printf(" %-25s | %lu\n", "Export Interval (seconds)", metricExportInterval);
}
}
exit(0);

View File

@@ -19,13 +19,13 @@
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
#import "Source/santasyncservice/SNTSyncManager.h"
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol>
@property MOLXPCConnection *listener;
@property SNTCommandSyncManager *syncManager;
@property SNTSyncManager *syncManager;
@end
@implementation SNTCommandSync
@@ -68,8 +68,8 @@ REGISTER_COMMAND_NAME(@"sync")
}
BOOL daemon = [arguments containsObject:@"--daemon"];
self.syncManager = [[SNTCommandSyncManager alloc] initWithDaemonConnection:self.daemonConn
isDaemon:daemon];
self.syncManager = [[SNTSyncManager alloc] initWithDaemonConnection:self.daemonConn
isDaemon:daemon];
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
// sandbox errors, which are benign but annoying. This line disables the cache entirely.

View File

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

View File

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

View File

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

View File

@@ -12,12 +12,14 @@ objc_library(
"DataLayer/SNTEventTable.m",
"DataLayer/SNTRuleTable.h",
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTDeviceManager.h",
"EventProviders/SNTDeviceManager.mm",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTEventProvider.h",
"Logs/SNTEventLog.h",
"Logs/SNTEventLog.m",
@@ -52,7 +54,6 @@ objc_library(
"IOKit",
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
@@ -61,43 +62,80 @@ objc_library(
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTPrefixTree",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SantaCache",
"//Source/santad:SNTApplicationCoreMetrics",
"@FMDB",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
objc_library(
name = "SNTApplicationCoreMetrics",
srcs = ["SNTApplicationCoreMetrics.m"],
hdrs = ["SNTApplicationCoreMetrics.h"],
deps = [
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
],
)
objc_library(
name = "EndpointSecurityTestLib",
testonly = 1,
srcs = [
"EventProviders/EndpointSecurityTestUtil.h",
"EventProviders/EndpointSecurityTestUtil.mm",
],
testonly = 1,
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
sdk_frameworks = [
"DiskArbitration",
"IOKit",
],
)
objc_library(
name = "DiskArbitrationTestLib",
testonly = 1,
srcs = [
"EventProviders/DiskArbitrationTestUtil.h",
"EventProviders/DiskArbitrationTestUtil.mm",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
sdk_frameworks = [
"DiskArbitration",
"IOKit",
],
)
macos_bundle(
name = "com.google.santa.daemon",
bundle_extension = "systemextension",
bundle_id = "com.google.santa.daemon",
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Daemon_Dev.provisionprofile",
@@ -110,37 +148,14 @@ macos_bundle(
santa_unit_test(
name = "SNTExecutionControllerTest",
srcs = [
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTDatabaseTable.m",
"DataLayer/SNTEventTable.h",
"DataLayer/SNTEventTable.m",
"DataLayer/SNTRuleTable.h",
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEventProvider.h",
"Logs/SNTEventLog.h",
"Logs/SNTEventLog.m",
"SNTDatabaseController.h",
"SNTDatabaseController.m",
"SNTExecutionController.h",
"SNTExecutionController.m",
"SNTExecutionControllerTest.m",
"SNTNotificationQueue.h",
"SNTNotificationQueue.m",
"SNTPolicyProcessor.h",
"SNTPolicyProcessor.m",
"SNTSyncdQueue.h",
"SNTSyncdQueue.m",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
"//Source/common:SantaCache",
":santad_lib",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
@@ -148,10 +163,12 @@ santa_unit_test(
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTPrefixTree",
"//Source/common:SNTRule",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SantaCache",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
@@ -201,10 +218,15 @@ santa_unit_test(
santa_unit_test(
name = "SNTEndpointSecurityManagerTest",
srcs = [
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEndpointSecurityManagerTest.mm",
"EventProviders/SNTEventProvider.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEndpointSecurityManager.h",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
@@ -212,31 +234,98 @@ santa_unit_test(
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
)
santa_unit_test(
name = "SNTApplicationTest",
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
name = "SNTDeviceManagerTest",
srcs = [
"SNTApplicationTest.m"
],
deps = [
":santad_lib",
":EndpointSecurityTestLib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
"EventProviders/SNTDeviceManagerTest.mm",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":DiskArbitrationTestLib",
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationTest",
srcs = [
"SNTApplicationTest.m",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationBenchmark",
srcs = [
"SNTApplicationBenchmark.m",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationCoreMetricsTest",
srcs = [
"SNTApplicationCoreMetricsTest.m",
],
minimum_os_version = "10.15",
deps = [
":SNTApplicationCoreMetrics",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
"@OCMock",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTApplicationCoreMetricsTest",
":SNTApplicationTest",
":SNTEndpointSecurityManagerTest",
":SNTEventTableTest",
":SNTExecutionControllerTest",
":SNTRuleTableTest",
],
visibility = ["//:santa_package_group"],
)

View File

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

View File

@@ -39,11 +39,25 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
@implementation SNTRuleTable
// ES on Monterey now has a default mute set of paths that are automatically applied to each ES
// client. This mute set contains most (not all) AUTH event types for some paths that were deemed
// system critical.
// Retain this list for < 12.0 versions of ES, but we should be able to rely on the paths muted by
// default (visible with es_muted_paths_events any time after connecting a new client and before
// modifying any of the mute state).
+ (NSArray *)criticalSystemBinaryPaths {
return @[
@"/usr/libexec/trustd",
@"/usr/sbin/securityd",
@"/usr/libexec/xpcproxy",
@"/usr/libexec/amfid",
@"/usr/libexec/opendirectoryd",
@"/usr/libexec/runningboardd",
@"/usr/libexec/syspolicyd",
@"/usr/libexec/watchdogd",
@"/usr/libexec/cfprefsd",
@"/usr/sbin/securityd",
@"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/Resources/tccd",
@"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer",
@"/usr/sbin/ocspd",
@"/usr/lib/dyld",
@"/Applications/Santa.app/Contents/MacOS/Santa",
@@ -120,6 +134,11 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
[db executeUpdate:@"ALTER TABLE 'rules' ADD 'timestamp' INTEGER"];
newVersion = 3;
}
if (version < 4) {
// Rename `shasum` column to `identifier`.
[db executeUpdate:@"ALTER TABLE 'rules' RENAME COLUMN 'shasum' TO 'identifier'"];
newVersion = 4;
}
// Save signing info for launchd and santad. Used to ensure they are always allowed.
self.santadCSInfo = [[MOLCodesignChecker alloc] initWithSelf];
@@ -175,25 +194,36 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
return count;
}
- (NSUInteger)teamIDRuleCount {
__block NSUInteger count = 0;
[self inDatabase:^(FMDatabase *db) {
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=3"];
}];
return count;
}
- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
return [[SNTRule alloc] initWithShasum:[rs stringForColumn:@"shasum"]
state:[rs intForColumn:@"state"]
type:[rs intForColumn:@"type"]
customMsg:[rs stringForColumn:@"custommsg"]
timestamp:[rs intForColumn:@"timestamp"]];
return [[SNTRule alloc] initWithIdentifier:[rs stringForColumn:@"identifier"]
state:[rs intForColumn:@"state"]
type:[rs intForColumn:@"type"]
customMsg:[rs stringForColumn:@"custommsg"]
timestamp:[rs intForColumn:@"timestamp"]];
}
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256 {
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID {
__block SNTRule *rule;
// NOTE: This code is written with the intention that the binary rule is searched for first
// as Santa is designed to go with the most-specific rule possible. As such the query
// should have "ORDER BY type DESC" before the LIMIT, to ensure that is the case. However,
// in all tested versions of SQLite that ORDER BY clause is unnecessary: the query is
// performed 'as written' by doing 2 separate lookups in the index and the second is skipped
// if the first returns a result. That behavior can be checked here:
// http://sqlfiddle.com/#!5/09c8a/2
// as Santa is designed to go with the most-specific rule possible.
//
// The intended order of precedence is Binaries > Certificates > Team IDs.
//
// As such the query should have "ORDER BY type DESC" before the LIMIT, to ensure that is the
// case. However, in all tested versions of SQLite that ORDER BY clause is unnecessary: the query
// is performed 'as written' by doing separate lookups in the index and the later lookups are if
// the first returns a result. That behavior can be checked here: http://sqlfiddle.com/#!5/cdc42/1
//
// Adding the ORDER BY clause slows down this query, particularly in a database where
// the number of binary rules outweighs the number of certificate rules because:
@@ -204,9 +234,9 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
//
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs =
[db executeQuery:
@"SELECT * FROM rules WHERE (shasum=? and type=1) OR (shasum=? AND type=2) LIMIT 1",
binarySHA256, certificateSHA256];
[db executeQuery:@"SELECT * FROM rules WHERE (identifier=? and type=1) OR "
@"(identifier=? AND type=2) OR (identifier=? AND type=3) LIMIT 1",
binarySHA256, certificateSHA256, teamID];
if ([rs next]) {
rule = [self ruleFromResultSet:rs];
}
@@ -216,11 +246,11 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
// Allow binaries signed by the "Software Signing" cert used to sign launchd
// if no existing rule has matched.
if (!rule && [certificateSHA256 isEqual:self.launchdCSInfo.leafCertificate.SHA256]) {
rule = [[SNTRule alloc] initWithShasum:certificateSHA256
state:SNTRuleStateAllow
type:SNTRuleTypeCertificate
customMsg:nil
timestamp:0];
rule = [[SNTRule alloc] initWithIdentifier:certificateSHA256
state:SNTRuleStateAllow
type:SNTRuleTypeCertificate
customMsg:nil
timestamp:0];
}
return rule;
@@ -244,7 +274,7 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
for (SNTRule *rule in rules) {
if (![rule isKindOfClass:[SNTRule class]] || rule.shasum.length == 0 ||
if (![rule isKindOfClass:[SNTRule class]] || rule.identifier.length == 0 ||
rule.state == SNTRuleStateUnknown || rule.type == SNTRuleTypeUnknown) {
[self fillError:error code:SNTRuleTableErrorInvalidRule message:rule.description];
*rollback = failed = YES;
@@ -252,7 +282,7 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
if (rule.state == SNTRuleStateRemove) {
if (![db executeUpdate:@"DELETE FROM rules WHERE shasum=? AND type=?", rule.shasum,
if (![db executeUpdate:@"DELETE FROM rules WHERE identifier=? AND type=?", rule.identifier,
@(rule.type)]) {
[self fillError:error code:SNTRuleTableErrorRemoveFailed message:[db lastErrorMessage]];
*rollback = failed = YES;
@@ -260,9 +290,9 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
} else {
if (![db executeUpdate:@"INSERT OR REPLACE INTO rules "
@"(shasum, state, type, custommsg, timestamp) "
@"(identifier, state, type, custommsg, timestamp) "
@"VALUES (?, ?, ?, ?, ?);",
rule.shasum, @(rule.state), @(rule.type), rule.customMsg,
rule.identifier, @(rule.state), @(rule.type), rule.customMsg,
@(rule.timestamp)]) {
[self fillError:error
code:SNTRuleTableErrorInsertOrReplaceFailed
@@ -284,7 +314,7 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
// If still here, then all rules in the array are allowlist rules. So now we look for allowlist
// rules where there is a previously existing allowlist compiler rule for the same shasum.
// rules where there is a previously existing allowlist compiler rule for the same identifier.
// If so we find such a rule, then cache should be flushed.
__block BOOL flushDecisionCache = NO;
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
@@ -293,8 +323,8 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
if (rule.type == SNTRuleTypeCertificate) continue;
if ([db longForQuery:
@"SELECT COUNT(*) FROM rules WHERE shasum=? AND type=? AND state=? LIMIT 1",
rule.shasum, @(SNTRuleTypeBinary), @(SNTRuleStateAllowCompiler)] > 0) {
@"SELECT COUNT(*) FROM rules WHERE identifier=? AND type=? AND state=? LIMIT 1",
rule.identifier, @(SNTRuleTypeBinary), @(SNTRuleStateAllowCompiler)] > 0) {
flushDecisionCache = YES;
break;
}
@@ -309,9 +339,9 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
if (!rule) return;
[rule resetTimestamp];
[self inDatabase:^(FMDatabase *db) {
if (![db executeUpdate:@"UPDATE rules SET timestamp=? WHERE shasum=? AND type=?",
@(rule.timestamp), rule.shasum, @(rule.type)]) {
LOGE(@"Could not update timestamp for rule with sha256=%@", rule.shasum);
if (![db executeUpdate:@"UPDATE rules SET timestamp=? WHERE identifier=? AND type=?",
@(rule.timestamp), rule.identifier, @(rule.type)]) {
LOGE(@"Could not update timestamp for rule with sha256=%@", rule.identifier);
}
}];
}

View File

@@ -34,9 +34,18 @@
self.sut = [[SNTRuleTable alloc] initWithDatabaseQueue:self.dbq];
}
- (SNTRule *)_exampleTeamIDRule {
SNTRule *r = [[SNTRule alloc] init];
r.identifier = @"teamID";
r.state = SNTRuleStateBlock;
r.type = SNTRuleTypeTeamID;
r.customMsg = @"A teamID rule";
return r;
}
- (SNTRule *)_exampleBinaryRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"a";
r.identifier = @"a";
r.state = SNTRuleStateBlock;
r.type = SNTRuleTypeBinary;
r.customMsg = @"A rule";
@@ -45,7 +54,7 @@
- (SNTRule *)_exampleCertRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"b";
r.identifier = @"b";
r.state = SNTRuleStateAllow;
r.type = SNTRuleTypeCertificate;
return r;
@@ -103,7 +112,7 @@
- (void)testAddInvalidRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"a";
r.identifier = @"a";
r.type = SNTRuleTypeCertificate;
NSError *error;
@@ -116,12 +125,12 @@
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary);
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil];
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil teamID:nil];
XCTAssertNil(r);
}
@@ -130,26 +139,52 @@
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b"];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b" teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"b");
XCTAssertEqualObjects(r.identifier, @"b");
XCTAssertEqual(r.type, SNTRuleTypeCertificate);
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a"];
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil];
XCTAssertNil(r);
}
- (void)testFetchTeamIDRule {
[self.sut addRules:@[ [self _exampleBinaryRule], [self _exampleTeamIDRule] ]
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"teamID");
XCTAssertEqual(r.type, SNTRuleTypeTeamID);
XCTAssertEqual([self.sut teamIDRuleCount], 1);
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"nonexistentTeamID"];
XCTAssertNil(r);
}
- (void)testFetchRuleOrdering {
[self.sut addRules:@[ [self _exampleCertRule], [self _exampleBinaryRule] ]
cleanSlate:NO
error:nil];
[self.sut
addRules:@[ [self _exampleCertRule], [self _exampleBinaryRule], [self _exampleTeamIDRule] ]
cleanSlate:NO
error:nil];
// This test verifies that the implicit rule ordering we've been abusing is still working.
// See the comment in SNTRuleTable#ruleForBinarySHA256:certificateSHA256:
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"b"];
// See the comment in SNTRuleTable#ruleForBinarySHA256:certificateSHA256:teamID
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"b" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");
r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"unknowncert" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");
r = [self.sut ruleForBinarySHA256:@"unknown" certificateSHA256:@"b" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"b");
XCTAssertEqual(r.type, SNTRuleTypeCertificate, @"Implicit rule ordering failed");
}
- (void)testBadDatabase {

View File

@@ -0,0 +1,84 @@
/// 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.
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// Mock object to point the opaque DADiskRefs to instead.
// Note that this will have undefined behavior for DA functions that aren't
// shimmed out by this utility, as the original DADiskRef refers to a completely
// different struct managed by the CFRuntime.
// https://opensource.apple.com/source/DiskArbitration/DiskArbitration-297.70.1/DiskArbitration/DADisk.c.auto.html
@interface MockDADisk : NSObject
@property(nonatomic) NSDictionary *diskDescription;
@property(nonatomic, readwrite) NSString *name;
@end
typedef void (^MockDADiskAppearedCallback)(DADiskRef ref);
// Singleton mock fixture around all of the DiskArbitration framework functions
@interface MockDiskArbitration : NSObject
@property(nonatomic, readwrite, nonnull)
NSMutableDictionary<NSString *, MockDADisk *> *insertedDevices;
@property(nonatomic, readwrite, nonnull)
NSMutableArray<MockDADiskAppearedCallback> *diskAppearedCallbacks;
@property(nonatomic) BOOL wasRemounted;
@property(nonatomic, nullable) dispatch_queue_t sessionQueue;
- (instancetype _Nonnull)init;
- (void)reset;
// Also triggers DADiskRegisterDiskAppearedCallback
- (void)insert:(MockDADisk *)ref bsdName:(NSString *)bsdName;
// Retrieve an initialized singleton MockDiskArbitration object
+ (instancetype _Nonnull)mockDiskArbitration;
@end
//
// All DiskArbitration functions used in SNTDeviceManager and shimmed out accordingly.
//
CF_EXTERN_C_BEGIN
void DADiskMountWithArguments(DADiskRef _Nonnull disk, CFURLRef __nullable path,
DADiskMountOptions options, DADiskMountCallback __nullable callback,
void *__nullable context,
CFStringRef __nullable arguments[_Nullable]);
DADiskRef __nullable DADiskCreateFromBSDName(CFAllocatorRef __nullable allocator,
DASessionRef session, const char *name);
CFDictionaryRef __nullable DADiskCopyDescription(DADiskRef disk);
void DARegisterDiskAppearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskAppearedCallback callback, void *__nullable context);
void DARegisterDiskDisappearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskDisappearedCallback callback,
void *__nullable context);
void DARegisterDiskDescriptionChangedCallback(DASessionRef session,
CFDictionaryRef __nullable match,
CFArrayRef __nullable watch,
DADiskDescriptionChangedCallback callback,
void *__nullable context);
void DASessionSetDispatchQueue(DASessionRef session, dispatch_queue_t __nullable queue);
DASessionRef __nullable DASessionCreate(CFAllocatorRef __nullable allocator);
CF_EXTERN_C_END
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,120 @@
/// 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>
#include <stdlib.h>
#import "Source/santad/EventProviders/DiskArbitrationTestUtil.h"
NS_ASSUME_NONNULL_BEGIN
@implementation MockDADisk
@end
@implementation MockDiskArbitration
- (instancetype _Nonnull)init {
self = [super init];
if (self) {
_insertedDevices = [NSMutableDictionary dictionary];
_diskAppearedCallbacks = [NSMutableArray array];
}
return self;
}
- (void)reset {
[self.insertedDevices removeAllObjects];
[self.diskAppearedCallbacks removeAllObjects];
self.sessionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.wasRemounted = NO;
}
- (void)insert:(MockDADisk *)ref bsdName:(NSString *)bsdName {
self.insertedDevices[bsdName] = ref;
for (MockDADiskAppearedCallback callback in self.diskAppearedCallbacks) {
dispatch_sync(self.sessionQueue, ^{
callback((__bridge DADiskRef)ref);
});
}
}
// Retrieve an initialized singleton MockDiskArbitration object
+ (instancetype _Nonnull)mockDiskArbitration {
static MockDiskArbitration *sharedES;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedES = [[MockDiskArbitration alloc] init];
});
return sharedES;
};
@end
void DADiskMountWithArguments(DADiskRef _Nonnull disk, CFURLRef __nullable path,
DADiskMountOptions options, DADiskMountCallback __nullable callback,
void *__nullable context,
CFStringRef __nullable arguments[_Nullable]) {
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
mockDA.wasRemounted = YES;
}
DADiskRef __nullable DADiskCreateFromBSDName(CFAllocatorRef __nullable allocator,
DASessionRef session, const char *name) {
NSString *nsName = [NSString stringWithUTF8String:name];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
MockDADisk *got = mockDA.insertedDevices[nsName];
DADiskRef ref = (__bridge DADiskRef)got;
CFRetain(ref);
return ref;
}
CFDictionaryRef __nullable DADiskCopyDescription(DADiskRef disk) {
CFDictionaryRef description = NULL;
if (disk) {
MockDADisk *mockDisk = (__bridge MockDADisk *)disk;
description = (__bridge_retained CFDictionaryRef)mockDisk.diskDescription;
}
return description;
}
void DARegisterDiskAppearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskAppearedCallback callback, void *__nullable context) {
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA.diskAppearedCallbacks addObject:^(DADiskRef ref) {
callback(ref, context);
}];
}
void DARegisterDiskDisappearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskDisappearedCallback callback,
void *__nullable context){};
void DARegisterDiskDescriptionChangedCallback(DASessionRef session,
CFDictionaryRef __nullable match,
CFArrayRef __nullable watch,
DADiskDescriptionChangedCallback callback,
void *__nullable context){};
void DASessionSetDispatchQueue(DASessionRef session, dispatch_queue_t __nullable queue) {
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
mockDA.sessionQueue = queue;
};
DASessionRef __nullable DASessionCreate(CFAllocatorRef __nullable allocator) {
return (__bridge DASessionRef)[MockDiskArbitration mockDiskArbitration];
};
NS_ASSUME_NONNULL_END

View File

@@ -46,7 +46,7 @@ typedef void (^ESCallback)(ESResponse *_Nonnull);
@interface MockEndpointSecurity : NSObject
@property NSMutableArray *_Nonnull subscriptions;
- (void)reset;
- (void)registerResponseCallback:(ESCallback _Nonnull)callback;
- (void)registerResponseCallback:(es_event_type_t)t withCallback:(ESCallback _Nonnull)callback;
- (void)triggerHandler:(es_message_t *_Nonnull)msg;
/// Retrieve an initialized singleton MockEndpointSecurity object

View File

@@ -89,19 +89,22 @@ CF_EXTERN_C_END
@implementation ESResponse
@end
@interface MockEndpointSecurity ()
@property NSMutableArray<ESCallback> *responseCallbacks;
@property NSObject *client;
@interface MockESClient : NSObject
@property NSMutableArray *_Nonnull subscriptions;
@property es_handler_block_t handler;
@end
@implementation MockEndpointSecurity
@implementation MockESClient
- (instancetype)init {
self = [super init];
if (self) {
_responseCallbacks = [NSMutableArray array];
_subscriptions = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
[self resetSubscriptions];
@synchronized(self) {
_subscriptions = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
for (size_t i = 0; i < ES_EVENT_TYPE_LAST; i++) {
[self.subscriptions addObject:@NO];
}
}
}
return self;
};
@@ -112,31 +115,78 @@ CF_EXTERN_C_END
}
}
- (void)triggerHandler:(es_message_t *_Nonnull)msg {
self.handler((__bridge es_client_t *_Nullable)self, msg);
}
- (void)dealloc {
@synchronized(self) {
[self.subscriptions removeAllObjects];
}
}
@end
@interface MockEndpointSecurity ()
@property NSMutableArray<MockESClient *> *clients;
// Array of collections of ESCallback blocks
// This should be of size ES_EVENT_TYPE_LAST, allowing for indexing by ES_EVENT_TYPE_xxx members.
@property NSMutableArray<NSMutableArray<ESCallback> *> *responseCallbacks;
@end
@implementation MockEndpointSecurity
- (instancetype)init {
self = [super init];
if (self) {
@synchronized(self) {
_clients = [NSMutableArray array];
_responseCallbacks = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
for (size_t i = 0; i < ES_EVENT_TYPE_LAST; i++) {
[self.responseCallbacks addObject:[NSMutableArray array]];
}
[self reset];
}
}
return self;
};
- (void)resetResponseCallbacks {
for (NSMutableArray *callback in self.responseCallbacks) {
if (callback != nil) {
[callback removeAllObjects];
}
}
}
- (void)reset {
@synchronized(self) {
[self.responseCallbacks removeAllObjects];
self.handler = nil;
self.client = nil;
[self.clients removeAllObjects];
[self resetResponseCallbacks];
}
};
- (void)newClient:(es_client_t *_Nullable *_Nonnull)client
handler:(es_handler_block_t __strong)handler {
// es_client_t is generally used as a pointer to an opaque struct (secretly a mach port).
// We just want to set it to something nonnull for passing initialization checks. It shouldn't
// ever be directly dereferenced.
self.client = [[NSObject alloc] init];
*client = (__bridge es_client_t *)self.client;
self.handler = handler;
// There is also a few nonnull initialization checks on it.
MockESClient *mockClient = [[MockESClient alloc] init];
*client = (__bridge es_client_t *)mockClient;
mockClient.handler = handler;
[self.clients addObject:mockClient];
}
- (void)triggerHandler:(es_message_t *_Nonnull)msg {
self.handler((__bridge es_client_t *_Nullable)self.client, msg);
for (MockESClient *client in self.clients) {
if (client.subscriptions[msg->event_type]) {
[client triggerHandler:msg];
}
}
}
- (void)registerResponseCallback:(ESCallback _Nonnull)callback {
- (void)registerResponseCallback:(es_event_type_t)t withCallback:(ESCallback _Nonnull)callback {
@synchronized(self) {
[self.responseCallbacks addObject:callback];
[self.responseCallbacks[t] addObject:callback];
}
}
@@ -147,7 +197,7 @@ CF_EXTERN_C_END
ESResponse *response = [[ESResponse alloc] init];
response.result = result;
response.shouldCache = cache;
for (void (^callback)(ESResponse *) in self.responseCallbacks) {
for (void (^callback)(ESResponse *) in self.responseCallbacks[msg->event_type]) {
callback(response);
}
}
@@ -156,10 +206,22 @@ CF_EXTERN_C_END
- (void)setSubscriptions:(const es_event_type_t *_Nonnull)events
event_count:(uint32_t)event_count
value:(NSNumber *)value {
value:(NSNumber *)value
client:(es_client_t *)client {
@synchronized(self) {
MockESClient *toUpdate = nil;
for (MockESClient *c in self.clients) {
if (client == (__bridge es_client_t *)c) {
toUpdate = c;
}
}
if (toUpdate == nil) {
NSLog(@"setting subscription for unknown client");
return;
}
for (size_t i = 0; i < event_count; i++) {
self.subscriptions[events[i]] = value;
toUpdate.subscriptions[events[i]] = value;
}
}
}
@@ -212,7 +274,8 @@ es_return_t es_subscribe(es_client_t *_Nonnull client, const es_event_type_t *_N
uint32_t event_count) {
[[MockEndpointSecurity mockEndpointSecurity] setSubscriptions:events
event_count:event_count
value:@YES];
value:@YES
client:client];
return ES_RETURN_SUCCESS;
}
API_AVAILABLE(macos(10.15))
@@ -221,7 +284,8 @@ es_return_t es_unsubscribe(es_client_t *_Nonnull client, const es_event_type_t *
uint32_t event_count) {
[[MockEndpointSecurity mockEndpointSecurity] setSubscriptions:events
event_count:event_count
value:@NO];
value:@NO
client:client];
return ES_RETURN_SUCCESS;
};

View File

@@ -0,0 +1,33 @@
/// 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 <DiskArbitration/DiskArbitration.h>
#import <Foundation/Foundation.h>
#include <EndpointSecurity/EndpointSecurity.h>
/*
* Manages DiskArbitration and EndpointSecurity to monitor/block/remount USB
* storage devices.
*/
@interface SNTDeviceManager : NSObject
@property(nonatomic, readwrite) BOOL subscribed;
@property(nonatomic, readwrite) BOOL blockUSBMount;
@property(nonatomic, readwrite, nullable) NSArray<NSString *> *remountArgs;
- (instancetype _Nonnull)init;
- (void)listen;
- (BOOL)subscribed;
@end

View File

@@ -0,0 +1,300 @@
/// 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/santad/EventProviders/SNTDeviceManager.h"
#import <DiskArbitration/DiskArbitration.h>
#import <Foundation/Foundation.h>
#include <bsm/libbsm.h>
#include <errno.h>
#include <libproc.h>
#include <sys/mount.h>
#include <atomic>
#include <memory>
#import "Source/common/SNTLogging.h"
#import "Source/santad/Logs/SNTEventLog.h"
void diskMountedCallback(DADiskRef disk, DADissenterRef dissenter, void *context) {
if (dissenter) {
DAReturn status = DADissenterGetStatus(dissenter);
NSString *statusString = (NSString *)DADissenterGetStatusString(dissenter);
IOReturn systemCode = err_get_system(status);
IOReturn subSystemCode = err_get_sub(status);
IOReturn errorCode = err_get_code(status);
LOGE(
@"SNTDeviceManager: dissenter status codes: system: %d, subsystem: %d, err: %d; status: %s",
systemCode, subSystemCode, errorCode, [statusString UTF8String]);
}
}
void diskAppearedCallback(DADiskRef disk, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
SNTEventLog *logger = [SNTEventLog logger];
if (logger) [logger logDiskAppeared:props];
}
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
if (props[@"DAVolumePath"]) {
SNTEventLog *logger = [SNTEventLog logger];
if (logger) [logger logDiskAppeared:props];
}
}
void diskDisappearedCallback(DADiskRef disk, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
SNTEventLog *logger = [SNTEventLog logger];
if (logger) [logger logDiskDisappeared:props];
}
NSArray<NSString *> *maskToMountArgs(long remountOpts) {
NSMutableArray<NSString *> *args = [NSMutableArray array];
if (remountOpts & MNT_RDONLY) [args addObject:@"rdonly"];
if (remountOpts & MNT_NOEXEC) [args addObject:@"noexec"];
if (remountOpts & MNT_NOSUID) [args addObject:@"nosuid"];
if (remountOpts & MNT_DONTBROWSE) [args addObject:@"nobrowse"];
if (remountOpts & MNT_UNKNOWNPERMISSIONS) [args addObject:@"noowners"];
if (remountOpts & MNT_NODEV) [args addObject:@"nodev"];
if (remountOpts & MNT_JOURNALED) [args addObject:@"-j"];
if (remountOpts & MNT_ASYNC) [args addObject:@"async"];
return args;
}
long mountArgsToMask(NSArray<NSString *> *args) {
long flags = 0;
for (NSString *i in args) {
NSString *arg = [i lowercaseString];
if ([arg isEqualToString:@"rdonly"])
flags |= MNT_RDONLY;
else if ([arg isEqualToString:@"noexec"])
flags |= MNT_NOEXEC;
else if ([arg isEqualToString:@"nosuid"])
flags |= MNT_NOSUID;
else if ([arg isEqualToString:@"nobrowse"])
flags |= MNT_DONTBROWSE;
else if ([arg isEqualToString:@"noowners"])
flags |= MNT_UNKNOWNPERMISSIONS;
else if ([arg isEqualToString:@"nodev"])
flags |= MNT_NODEV;
else if ([arg isEqualToString:@"-j"])
flags |= MNT_JOURNALED;
else if ([arg isEqualToString:@"async"])
flags |= MNT_ASYNC;
else
LOGE(@"SNTDeviceManager: unexpected mount arg: %@", arg);
}
return flags;
}
@interface SNTDeviceManager ()
@property DASessionRef diskArbSession;
@property(nonatomic, readonly) es_client_t *client;
@property(nonatomic, readonly) dispatch_queue_t esAuthQueue;
@property(nonatomic, readonly) dispatch_queue_t diskQueue;
@end
@implementation SNTDeviceManager
- (instancetype _Nonnull)init API_AVAILABLE(macos(10.15)) {
self = [super init];
if (self) {
_blockUSBMount = false;
_diskQueue = dispatch_queue_create("com.google.santad.disk_queue", DISPATCH_QUEUE_SERIAL);
_esAuthQueue =
dispatch_queue_create("com.google.santa.daemon.es_device_auth", DISPATCH_QUEUE_CONCURRENT);
_diskArbSession = DASessionCreate(NULL);
DASessionSetDispatchQueue(_diskArbSession, _diskQueue);
if (@available(macos 10.15, *)) [self initES];
}
return self;
}
- (void)initES API_AVAILABLE(macos(10.15)) {
while (!self.client) {
es_client_t *client = NULL;
es_new_client_result_t ret = es_new_client(&client, ^(es_client_t *c, const es_message_t *m) {
// Set timeout to 5 seconds before the ES deadline.
[self handleESMessageWithTimeout:m
withClient:c
timeout:dispatch_time(m->deadline, NSEC_PER_SEC * -5)];
});
switch (ret) {
case ES_NEW_CLIENT_RESULT_SUCCESS:
LOGI(@"Connected to EndpointSecurity");
_client = client;
return;
case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
LOGE(@"Unable to create EndpointSecurity client, not full-disk access permitted");
LOGE(@"Sleeping for 30s before restarting.");
sleep(30);
exit(ret);
default:
LOGE(@"Unable to create es client: %d. Sleeping for a minute.", ret);
sleep(60);
continue;
}
}
}
- (void)listenES API_AVAILABLE(macos(10.15)) {
while (!self.client)
usleep(100000); // 100ms
es_event_type_t events[] = {
ES_EVENT_TYPE_AUTH_MOUNT,
};
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
if (sret != ES_RETURN_SUCCESS)
LOGE(@"SNTDeviceManager: unable to subscribe to auth mount events: %d", sret);
}
- (void)listenDA {
DARegisterDiskAppearedCallback(_diskArbSession, NULL, diskAppearedCallback,
(__bridge void *)self);
DARegisterDiskDescriptionChangedCallback(_diskArbSession, NULL, NULL,
diskDescriptionChangedCallback, (__bridge void *)self);
DARegisterDiskDisappearedCallback(_diskArbSession, NULL, diskDisappearedCallback,
(__bridge void *)self);
}
- (void)listen {
[self listenDA];
if (@available(macos 10.15, *)) [self listenES];
self.subscribed = YES;
}
- (void)handleAuthMount:(const es_message_t *)m
withClient:(es_client_t *)c API_AVAILABLE(macos(10.15)) {
if (!self.blockUSBMount) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
long mountMode = m->event.mount.statfs->f_flags;
pid_t pid = audit_token_to_pid(m->process->audit_token);
LOGI(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
m->process->executable->path.data, pid, mountMode);
DADiskRef disk =
DADiskCreateFromBSDName(NULL, self.diskArbSession, m->event.mount.statfs->f_mntfromname);
CFAutorelease(disk);
// TODO(tnek): Log all of the other attributes available in diskInfo into a structured log format.
NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk));
BOOL isRemovable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaRemovableKey] boolValue];
BOOL isUSB =
[diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey] isEqualTo:@"USB"];
if (!isRemovable || !isUSB) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
if (shouldRemount) {
long remountOpts = mountArgsToMask(self.remountArgs);
if (mountMode & remountOpts) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
long newMode = mountMode | remountOpts;
LOGI(@"SNTDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)",
m->event.mount.statfs->f_mntfromname, m->event.mount.statfs->f_mntonname, mountMode,
newMode);
[self remount:disk mountMode:newMode];
}
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
}
- (void)remount:(DADiskRef)disk mountMode:(long)remountMask {
NSArray<NSString *> *args = maskToMountArgs(remountMask);
CFStringRef *argv = (CFStringRef *)calloc(args.count + 1, sizeof(CFStringRef));
CFArrayGetValues((__bridge CFArrayRef)args, CFRangeMake(0, (CFIndex)args.count),
(const void **)argv);
DADiskMountWithArguments(disk, NULL, kDADiskMountOptionDefault, diskMountedCallback,
(__bridge void *)self, (CFStringRef *)argv);
free(argv);
}
// handleESMessage handles an ES message synchronously. This will block all incoming ES events
// until either we serve a response or we hit the auth deadline. Prefer [SNTDeviceManager
// handleESMessageWithTimeout]
// TODO(tnek): generalize this timeout handling logic so that EndpointSecurityManager can use it
// too.
- (void)handleESMessageWithTimeout:(const es_message_t *)m
withClient:(es_client_t *)c
timeout:(dispatch_time_t)timeout API_AVAILABLE(macos(10.15)) {
// ES will kill our whole client if we don't meet the es_message auth deadline, so we try to
// gracefully handle it with a deny-by-default in the worst-case before it can do that.
// This isn't an issue for notify events, so we're in no rush for those.
std::shared_ptr<std::atomic<bool>> responded;
if (m->action_type == ES_ACTION_TYPE_AUTH) {
responded = std::make_shared<std::atomic<bool>>(false);
dispatch_after(timeout, self.esAuthQueue, ^(void) {
if (responded->load()) return;
LOGE(@"SNTDeviceManager: deadline reached: deny pid=%d ret=%d",
audit_token_to_pid(m->process->audit_token),
es_respond_auth_result(c, m, ES_AUTH_RESULT_DENY, false));
});
}
// TODO(tnek): migrate to es_retain_message.
es_message_t *mc = es_copy_message(m);
dispatch_async(self.esAuthQueue, ^{
[self handleESMessage:m withClient:c];
if (m->action_type == ES_ACTION_TYPE_AUTH) {
responded->store(true);
}
es_free_message(mc);
});
}
- (void)handleESMessage:(const es_message_t *)m
withClient:(es_client_t *)c API_AVAILABLE(macos(10.15)) {
switch (m->event_type) {
case ES_EVENT_TYPE_AUTH_MOUNT: {
[self handleAuthMount:m withClient:c];
// Intentional fallthrough
[[fallthrough]];
}
// TODO(tnek): log any extra data here about mounts.
case ES_EVENT_TYPE_NOTIFY_MOUNT: {
break;
}
default: LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
}
}
@end

View File

@@ -0,0 +1,157 @@
/// 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 <DiskArbitration/DiskArbitration.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import <bsm/libbsm.h>
#include <sys/mount.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/santad/EventProviders/SNTDeviceManager.h"
#import "Source/santad/EventProviders/DiskArbitrationTestUtil.h"
#import "Source/santad/EventProviders/EndpointSecurityTestUtil.h"
@interface SNTDeviceManagerTest : XCTestCase
@property id mockConfigurator;
@end
@implementation SNTDeviceManagerTest
- (void)setUp {
[super setUp];
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator eventLogType]).andReturn(-1);
fclose(stdout);
}
- (ESResponse *)triggerTestMount:(SNTDeviceManager *)deviceManager
mockES:(MockEndpointSecurity *)mockES
mockDA:(MockDiskArbitration *)mockDA {
if (!deviceManager.subscribed) {
// [deviceManager listen] is synchronous, but we want to asynchronously dispatch it
// with an enforced timeout to ensure that we never run into issues where the client
// never instantiates.
XCTestExpectation *initExpectation =
[self expectationWithDescription:@"Wait for SNTDeviceManager to instantiate"];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
[deviceManager listen];
});
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
while (!deviceManager.subscribed)
;
[initExpectation fulfill];
});
[self waitForExpectations:@[ initExpectation ] timeout:60.0];
}
struct statfs *fs = static_cast<struct statfs *>(calloc(1, sizeof(struct statfs)));
NSString *test_mntfromname = @"/dev/disk2s1";
NSString *test_mntonname = @"/Volumes/KATE'S 4G";
const char *c_mntfromname = [test_mntfromname UTF8String];
const char *c_mntonname = [test_mntonname UTF8String];
strncpy(fs->f_mntfromname, c_mntfromname, MAXPATHLEN);
strncpy(fs->f_mntonname, c_mntonname, MAXPATHLEN);
MockDADisk *disk = [[MockDADisk alloc] init];
disk.diskDescription = @{
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey : @"USB",
(__bridge NSString *)kDADiskDescriptionMediaRemovableKey : @YES,
@"DAVolumeMountable" : @YES,
@"DAVolumePath" : test_mntonname,
@"DADeviceModel" : @"Some device model",
@"DADevicePath" : test_mntonname,
@"DADeviceVendor" : @"Some vendor",
@"DAAppearanceTime" : @0,
@"DAMediaBSDName" : test_mntfromname,
};
[mockDA insert:disk bsdName:test_mntfromname];
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
m.binaryPath = @"/System/Library/Filesystems/msdos.fs/Contents/Resources/mount_msdos";
m.message->action_type = ES_ACTION_TYPE_AUTH;
m.message->event_type = ES_EVENT_TYPE_AUTH_MOUNT;
m.message->event = (es_events_t){.mount = {.statfs = fs}};
}];
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_MOUNT
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES triggerHandler:m.message];
[self waitForExpectations:@[ expectation ] timeout:60.0];
free(fs);
return got;
}
- (void)testUSBBlockDisabled {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = NO;
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
}
- (void)testRemount {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = YES;
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
XCTAssertEqual(mockDA.wasRemounted, YES);
}
- (void)testBlockNoRemount {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = YES;
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
XCTAssertEqual(mockDA.wasRemounted, NO);
}
@end

View File

@@ -352,6 +352,7 @@
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
return;
}
case ES_EVENT_TYPE_NOTIFY_CLOSE: {
sm.action = ACTION_NOTIFY_WRITE;
targetFile = m->event.close.target;

View File

@@ -49,15 +49,16 @@ const NSString *const kBenignPath = @"/some/other/path";
[self expectationWithDescription:@"Wait for santa's Auth dispatch queue"];
__block NSMutableArray<ESResponse *> *events = [NSMutableArray array];
[mockES registerResponseCallback:^(ESResponse *r) {
@synchronized(self) {
[events addObject:r];
}
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_UNLINK
withCallback:^(ESResponse *r) {
@synchronized(self) {
[events addObject:r];
}
if (events.count >= wantNumResp) {
[expectation fulfill];
}
}];
if (events.count >= wantNumResp) {
[expectation fulfill];
}
}];
__block es_file_t dbFile = {.path = MakeStringToken(kEventsDBPath)};
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
@@ -94,10 +95,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_UNLINK
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t dbFile = {.path = MakeStringToken(testPath)};
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
@@ -125,10 +127,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_UNLINK
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t dbFile = {.path = MakeStringToken(@"/some/other/path")};
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
@@ -160,10 +163,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_RENAME
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t otherFile = {.path = MakeStringToken(@"/some/other/path")};
__block es_file_t dbFile = {.path = MakeStringToken(testPath)};
@@ -206,10 +210,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_RENAME
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t otherFile = {.path = MakeStringToken(@"/some/other/path")};
__block es_file_t dbFile = {.path = MakeStringToken(testPath)};

View File

@@ -14,15 +14,20 @@
#import <Foundation/Foundation.h>
#import "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTKernelCommon.h"
@class SNTCachedDecision;
@class SNTStoredEvent;
///
/// Logs execution and file write events to syslog
/// Abstract interface for logging execution and file write events to syslog
///
@interface SNTEventLog : NSObject
// Getter for a singleton SNTEventLog object.
// Determines which type of SNTEventLog to use based on [SNTConfigurator eventLogType].
+ (instancetype)logger;
// Methods implemented by a concrete subclass.
- (void)logDiskAppeared:(NSDictionary *)diskProperties;
- (void)logDiskDisappeared:(NSDictionary *)diskProperties;
@@ -34,7 +39,7 @@
- (void)logExit:(santa_message_t)message;
- (void)writeLog:(NSString *)log;
// Methods for storing, retrieving, and removing cached decisions.
// Methods for storing, retrieving, and removing cached decisions.
- (void)cacheDecision:(SNTCachedDecision *)cd;
- (SNTCachedDecision *)cachedDecisionForMessage:(santa_message_t)message;
- (void)forgetCachedDecisionForVnodeId:(santa_vnode_id_t)vnodeId;

View File

@@ -11,7 +11,6 @@
/// 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/santad/Logs/SNTEventLog.h"
#include <dlfcn.h>
@@ -25,6 +24,9 @@
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/SNTDatabaseController.h"
#import "Source/santad/Logs/SNTFileEventLog.h"
#import "Source/santad/Logs/SNTSyslogEventLog.h"
@interface SNTEventLog ()
@property NSMutableDictionary<NSNumber *, SNTCachedDecision *> *detailStore;
@property dispatch_queue_t detailStoreQueue;
@@ -50,6 +52,7 @@
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
_dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
_dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
// Grab the system UUID on init
@@ -120,10 +123,10 @@
if (cd.decision != SNTEventStateAllowTransitive) return;
NSDate *lastUpdate = [self.timestampResetMap objectForKey:cd.sha256];
if (!lastUpdate || -[lastUpdate timeIntervalSinceNow] > 3600) {
SNTRule *rule = [[SNTRule alloc] initWithShasum:cd.sha256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:nil];
SNTRule *rule = [[SNTRule alloc] initWithIdentifier:cd.sha256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:nil];
[[SNTDatabaseController ruleTable] resetTimestampForRule:rule];
[self.timestampResetMap setObject:[NSDate date] forKey:cd.sha256];
}
@@ -418,4 +421,22 @@
return [origURL path]; // this will be nil if there was an error
}
+ (instancetype)logger {
static SNTEventLog *logger;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
switch ([[SNTConfigurator configurator] eventLogType]) {
case SNTEventLogTypeSyslog: {
logger = [[SNTSyslogEventLog alloc] init];
break;
}
case SNTEventLogTypeFilelog: {
logger = [[SNTFileEventLog alloc] init];
break;
}
default: logger = nil;
}
});
return logger;
}
@end

View File

@@ -62,8 +62,15 @@
if (newpath) {
[outStr appendFormat:@"|newpath=%@", [self sanitizeString:newpath]];
}
char ppath[PATH_MAX] = "(null)";
proc_pidpath(message.pid, ppath, PATH_MAX);
if (message.es_message) {
es_message_t *m = message.es_message;
es_string_token_t path = m->process->executable->path;
strlcpy(ppath, path.data, sizeof(ppath));
} else {
proc_pidpath(message.pid, ppath, PATH_MAX);
}
[outStr
appendFormat:
@@ -113,6 +120,11 @@
r = @"SCOPE";
logArgs = YES;
break;
case SNTEventStateAllowTeamID:
d = @"ALLOW";
r = @"TEAMID";
logArgs = YES;
break;
case SNTEventStateAllowUnknown:
d = @"ALLOW";
r = @"UNKNOWN";
@@ -130,6 +142,10 @@
d = @"DENY";
r = @"SCOPE";
break;
case SNTEventStateBlockTeamID:
d = @"DENY";
r = @"TEAMID";
break;
case SNTEventStateBlockUnknown:
d = @"DENY";
r = @"UNKNOWN";

View File

@@ -13,25 +13,27 @@
/// limitations under the License.
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import <DiskArbitration/DiskArbitration.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCMetricServiceInterface.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTDeviceManager.h"
#import "Source/santad/EventProviders/SNTDriverManager.h"
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
#import "Source/santad/Logs/SNTFileEventLog.h"
#import "Source/santad/Logs/SNTSyslogEventLog.h"
#import "Source/santad/Logs/SNTEventLog.h"
#import "Source/santad/SNTCompilerController.h"
#import "Source/santad/SNTDaemonControlController.h"
#import "Source/santad/SNTDatabaseController.h"
@@ -40,14 +42,15 @@
#import "Source/santad/SNTSyncdQueue.h"
@interface SNTApplication ()
@property DASessionRef diskArbSession;
@property id<SNTEventProvider> eventProvider;
@property SNTEventLog *eventLog;
@property SNTExecutionController *execController;
@property SNTCompilerController *compilerController;
@property SNTDeviceManager *deviceManager;
@property MOLXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@property MOLXPCConnection *metricsConnection;
@property dispatch_source_t metricsTimer;
@end
@implementation SNTApplication
@@ -89,9 +92,10 @@
return nil;
}
switch ([configurator eventLogType]) {
case SNTEventLogTypeSyslog: _eventLog = [[SNTSyslogEventLog alloc] init]; break;
case SNTEventLogTypeFilelog: _eventLog = [[SNTFileEventLog alloc] init]; break;
_deviceManager = [[SNTDeviceManager alloc] init];
self.deviceManager.blockUSBMount = [configurator blockUSBMount];
if ([configurator remountUSBMode] != nil) {
self.deviceManager.remountArgs = [configurator remountUSBMode];
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
@@ -129,6 +133,23 @@
forKeyPath:NSStringFromSelector(@selector(blockedPathRegex))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(exportMetrics))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(metricExportInterval))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(blockUSBMount))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(remountUSBMode))
options:bits
context:NULL];
if (![configurator enableSystemExtension]) {
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(enableSystemExtension))
@@ -140,8 +161,7 @@
SNTDaemonControlController *dc =
[[SNTDaemonControlController alloc] initWithEventProvider:_eventProvider
notificationQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
syncdQueue:syncdQueue];
_controlConnection =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceID]];
@@ -152,20 +172,22 @@
[_controlConnection resume];
// Initialize the transitive whitelisting controller object.
_compilerController = [[SNTCompilerController alloc] initWithEventProvider:_eventProvider
eventLog:_eventLog];
_compilerController = [[SNTCompilerController alloc] initWithEventProvider:_eventProvider];
// Initialize the binary checker object
_execController = [[SNTExecutionController alloc] initWithEventProvider:_eventProvider
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
syncdQueue:syncdQueue];
// Start up santactl as a daemon if a sync server exists.
[self startSyncd];
if (!_execController) return nil;
if ([configurator exportMetrics]) {
[self startMetricsPoll];
}
}
return self;
@@ -176,7 +198,7 @@
[self performSelectorInBackground:@selector(beginListeningForDecisionRequests) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForLogRequests) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForDiskMounts) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForMountRequests) withObject:nil];
}
- (void)beginListeningForDecisionRequests {
@@ -216,59 +238,80 @@
NSString *path = @(message.path);
if (!path) break;
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
[self->_eventLog logFileModification:message];
[[SNTEventLog logger] logFileModification:message];
}
break;
}
case ACTION_NOTIFY_EXEC: {
[self->_eventLog logAllowedExecution:message];
[[SNTEventLog logger] logAllowedExecution:message];
break;
}
case ACTION_NOTIFY_FORK: [self->_eventLog logFork:message]; break;
case ACTION_NOTIFY_EXIT: [self->_eventLog logExit:message]; break;
case ACTION_NOTIFY_FORK: [[SNTEventLog logger] logFork:message]; break;
case ACTION_NOTIFY_EXIT: [[SNTEventLog logger] logExit:message]; break;
default: LOGE(@"Received log request without a valid action: %d", message.action); break;
}
}];
}
- (void)beginListeningForDiskMounts {
dispatch_queue_t disk_queue =
dispatch_queue_create("com.google.santad.disk_queue", DISPATCH_QUEUE_SERIAL);
_diskArbSession = DASessionCreate(NULL);
DASessionSetDispatchQueue(_diskArbSession, disk_queue);
DARegisterDiskAppearedCallback(_diskArbSession, NULL, diskAppearedCallback,
(__bridge void *)self);
DARegisterDiskDescriptionChangedCallback(_diskArbSession, NULL, NULL,
diskDescriptionChangedCallback, (__bridge void *)self);
DARegisterDiskDisappearedCallback(_diskArbSession, NULL, diskDisappearedCallback,
(__bridge void *)self);
- (void)beginListeningForMountRequests {
[self.deviceManager listen];
}
void diskAppearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
// Taken from Apple's Concurrency Programming Guide.
dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue,
dispatch_block_t block) {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
[app.eventLog logDiskAppeared:props];
if (timer) {
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
/*
* Create a SNTMetricSet instance and start reporting essential metrics immediately to the metric
* service.
*/
- (void)startMetricsPoll {
NSUInteger interval = [[SNTConfigurator configurator] metricExportInterval];
if (props[@"DAVolumePath"]) [app.eventLog logDiskAppeared:props];
LOGI(@"starting to export metrics every %ld seconds", interval);
void (^exportMetricsBlock)(void) = ^{
[[self.metricsConnection remoteObjectProxy]
exportForMonitoring:[[SNTMetricSet sharedInstance] export]];
};
static dispatch_once_t registerMetrics;
dispatch_once(&registerMetrics, ^{
_metricsConnection = [SNTXPCMetricServiceInterface configuredConnection];
[_metricsConnection resume];
LOGD(@"registering core metrics");
SNTRegisterCoreMetrics();
exportMetricsBlock();
});
dispatch_source_t timer = createDispatchTimer(interval * NSEC_PER_SEC, 1ull * NSEC_PER_SEC,
dispatch_get_main_queue(), exportMetricsBlock);
if (!timer) {
LOGE(@"failed to created timer for exporting metrics");
return;
}
_metricsTimer = timer;
}
void diskDisappearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
- (void)stopMetricsPoll {
if (!_metricsTimer) {
LOGE(@"stopMetricsPoll called while _metricsTimer is nil");
return;
}
[app.eventLog logDiskDisappeared:props];
[app.eventProvider flushCacheNonRootOnly:YES];
dispatch_source_cancel(_metricsTimer);
}
- (void)startSyncd {
@@ -331,6 +374,47 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
LOGI(@"The penultimate exit.");
exit(0);
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(exportMetrics))]) {
BOOL new = [ change[newKey] boolValue ];
BOOL old = [change[oldKey] boolValue];
if (old == NO && new == YES) {
LOGI(@"metricsExport changed NO -> YES, starting to export metrics");
[self startMetricsPoll];
} else if (old == YES && new == NO) {
LOGI(@"metricsExport changed YES -> NO, stopping export of metrics");
[self stopMetricsPoll];
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(metricExportInterval))]) {
// clang-format off
NSUInteger new = [ change[newKey] unsignedIntegerValue ];
NSUInteger old = [ change[oldKey] unsignedIntegerValue ];
// clang-format on
LOGI(@"MetricExportInterval changed from %ld to %ld restarting export", old, new);
[self stopMetricsPoll];
[self startMetricsPoll];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(blockUSBMount))]) {
BOOL new = [ change[newKey] boolValue ];
BOOL old = [change[oldKey] boolValue];
if (new != old) {
LOGI(@"BlockUSBMount changed: %d -> %d", old, new);
self.deviceManager.blockUSBMount = new;
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(remountUSBMode))]) {
NSArray<NSString *> *new = [ change[newKey] isKindOfClass : [NSArray class] ]
? (NSArray<NSString *> *)change[newKey]
: nil;
NSArray<NSString *> *old =
[change[oldKey] isKindOfClass:[NSArray class]] ? (NSArray<NSString *> *)change[oldKey] : nil;
if (![old isEqualToArray:new]) {
LOGI(@"RemountArgs changed: %s -> %s", [[old componentsJoinedByString:@","] UTF8String],
[[new componentsJoinedByString:@","] UTF8String]);
self.deviceManager.remountArgs = new;
}
}
}

View File

@@ -0,0 +1,153 @@
/// 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 <EndpointSecurity/EndpointSecurity.h>
#import <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTDatabaseController.h"
#include "Source/santad/EventProviders/EndpointSecurityTestUtil.h"
@interface SNTApplicationBenchmark : XCTestCase
@property id mockSNTDatabaseController;
@property id mockConfigurator;
@end
@implementation SNTApplicationBenchmark : XCTestCase
- (void)setUp {
[super setUp];
fclose(stdout);
self.mockSNTDatabaseController = OCMClassMock([SNTDatabaseController class]);
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator enableSystemExtension]).andReturn(true);
OCMStub([self.mockConfigurator enableSysxCache]).andReturn(false);
}
+ (NSArray<XCTPerformanceMetric> *)defaultPerformanceMetrics {
return @[
XCTPerformanceMetric_WallClockTime,
// Metrics visible and controllable from the XCode UI but without a symbol exposed for them:
@"com.apple.XCTPerformanceMetric_RunTime",
@"com.apple.XCTPerformanceMetric_UserTime",
@"com.apple.XCTPerformanceMetric_SystemTime",
@"com.apple.XCTPerformanceMetric_HighWaterMarkForHeapAllocations",
@"com.apple.XCTPerformanceMetric_PersistentHeapAllocations",
@"com.apple.XCTPerformanceMetric_PersistentHeapAllocationsNodes",
@"com.apple.XCTPerformanceMetric_PersistentVMAllocations",
@"com.apple.XCTPerformanceMetric_TotalHeapAllocationsKilobytes",
@"com.apple.XCTPerformanceMetric_TransientHeapAllocationsKilobytes",
@"com.apple.XCTPerformanceMetric_TransientHeapAllocationsNodes",
@"com.apple.XCTPerformanceMetric_TransientVMAllocationsKilobytes",
@"com.apple.XCTPerformanceMetric_HighWaterMarkForVMAllocations",
];
}
- (void)tearDown {
[self.mockSNTDatabaseController stopMocking];
[self.mockConfigurator stopMocking];
[super tearDown];
}
- (void)executeAndMeasure:(NSString *)binaryName testPath:(NSString *)testPath {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
OCMStub([self.mockSNTDatabaseController databasePath]).andReturn(testPath);
SNTApplication *app = [[SNTApplication alloc] init];
[app start];
// es events will start flowing in as soon as es_subscribe is called, regardless
// of whether we're ready or not for it.
XCTestExpectation *santaInit =
[self expectationWithDescription:@"Wait for Santa to subscribe to EndpointSecurity"];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
while ([mockES.subscriptions[ES_EVENT_TYPE_AUTH_EXEC] isEqualTo:@NO])
;
[santaInit fulfill];
});
// Ugly hack to deflake the test and allow listenForDecisionRequests to install the correct
// decision callback.
[self waitForExpectations:@[ santaInit ] timeout:2.0];
// MeasureMetrics actually runs all of the individual events asynchronously at once.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
void (^executeBinary)(void) = ^void(void) {
NSString *binaryPath = [NSString pathWithComponents:@[ testPath, binaryName ]];
struct stat fileStat;
lstat(binaryPath.UTF8String, &fileStat);
ESMessage *msg = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
m.binaryPath = binaryPath;
m.executable->stat = fileStat;
m.message->action_type = ES_ACTION_TYPE_AUTH;
m.message->event_type = ES_EVENT_TYPE_AUTH_EXEC;
m.message->event = (es_events_t){.exec = {.target = m.process}};
}];
__block BOOL complete = NO;
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_EXEC
withCallback:^(ESResponse *r) {
complete = YES;
}];
[self startMeasuring];
[mockES triggerHandler:msg.message];
while (!complete)
;
[self stopMeasuring];
dispatch_semaphore_signal(sem);
};
[self measureMetrics:[SNTApplicationBenchmark defaultPerformanceMetrics]
automaticallyStartMeasuring:false
forBlock:executeBinary];
int sampleSize = 10;
for (size_t i = 0; i < sampleSize; i++) {
dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
}
}
// Microbenchmarking analysis of binary execution
- (void)testMeasureExecutionDeny {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
]];
[self executeAndMeasure:@"badbinary" testPath:fullTestPath];
}
- (void)testMeasureExecutionAllow {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
]];
[self executeAndMeasure:@"goodbinary" testPath:fullTestPath];
}
@end

View File

@@ -0,0 +1,17 @@
/// 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/SNTMetricSet.h"
void SNTRegisterCoreMetrics();

View File

@@ -0,0 +1,142 @@
/// 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/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTSystemInfo.h"
#import <Foundation/Foundation.h>
#include <mach/mach.h>
#include <sys/resource.h>
/**
* Register the mode metric checking the config before reporting the status.
*/
static void RegisterModeMetric(SNTMetricSet *metricSet) {
SNTMetricStringGauge *mode = [metricSet stringGaugeWithName:@"/santa/mode"
fieldNames:@[]
helpText:@"Santa's operating mode"];
// create a callback that gets the current mode
[metricSet registerCallback:^{
SNTConfigurator *config = [SNTConfigurator configurator];
switch (config.clientMode) {
case SNTClientModeLockdown: [mode set:@"lockdown" forFieldValues:@[]]; break;
case SNTClientModeMonitor: [mode set:@"monitor" forFieldValues:@[]]; break;
default:
// Should never be reached.
[mode set:@"unknown" forFieldValues:@[]];
break;
}
}];
}
/**
* Register metrics for measuring memory usage.
*/
static void RegisterMemoryAndCPUMetrics(SNTMetricSet *metricSet) {
SNTMetricInt64Gauge *vsize =
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
fieldNames:@[]
helpText:@"The virtual memory size of this process"];
SNTMetricInt64Gauge *rsize =
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
fieldNames:@[]
helpText:@"The resident set size of this process"];
SNTMetricDoubleGauge *cpuUsage =
[metricSet doubleGaugeWithName:@"/proc/cpu_usage"
fieldNames:@[ @"mode" ] // "user" or "system"
helpText:@"CPU time consumed by this process, in seconds"];
[metricSet registerCallback:^(void) {
struct mach_task_basic_info info;
mach_msg_type_number_t size = MACH_TASK_BASIC_INFO_COUNT;
kern_return_t ret =
task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &size);
if (ret != KERN_SUCCESS) {
return;
}
[vsize set:info.virtual_size forFieldValues:@[]];
[rsize set:info.resident_size forFieldValues:@[]];
// convert times to seconds
double user_time = info.user_time.seconds + (info.user_time.microseconds / 1e6);
double system_time = info.system_time.seconds + (info.system_time.microseconds / 1e6);
[cpuUsage set:user_time forFieldValues:@[ @"user" ]];
[cpuUsage set:system_time forFieldValues:@[ @"system" ]];
}];
}
static void RegisterHostnameAndUsernameLabels(SNTMetricSet *metricSet) {
NSString *hostname = [NSProcessInfo processInfo].hostName;
[metricSet addRootLabel:@"host_name" value:hostname];
[metricSet addRootLabel:@"username" value:NSUserName()];
[metricSet addRootLabel:@"job_name" value:@"santad"];
[metricSet addRootLabel:@"service_name" value:@"santa"];
// get extra root labels from configuration
SNTConfigurator *config = [SNTConfigurator configurator];
NSDictionary *extraLabels = [config extraMetricLabels];
if (extraLabels.count == 0) return;
for (NSString *key in extraLabels) {
// remove the root label if the value is empty.
if ([@"" isEqualToString:(NSString *)extraLabels[key]]) {
[metricSet removeRootLabel:key];
continue;
}
// Set or override the value.
[metricSet addRootLabel:key value:(NSString *)extraLabels[key]];
}
}
static void RegisterCommonSantaMetrics(SNTMetricSet *metricSet) {
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
// register the version
[metricSet addConstantStringWithName:@"/build/label"
helpText:@"Version of the binary"
value:version];
// register start time
[metricSet
addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of Santad, in microseconds since epoch"
value:(long long)([[NSDate date] timeIntervalSince1970] * 1000000)];
// Register OS version
[metricSet addConstantStringWithName:@"/proc/os/version"
helpText:@"Short operating System version"
value:[SNTSystemInfo osVersion]];
RegisterModeMetric(metricSet);
// TODO(markowsky) Register CSR status
// TODO(markowsky) Register system extension status
}
void SNTRegisterCoreMetrics() {
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
RegisterHostnameAndUsernameLabels(metricSet);
RegisterMemoryAndCPUMetrics(metricSet);
RegisterCommonSantaMetrics(metricSet);
}

View File

@@ -0,0 +1,269 @@
/// 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/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTSystemInfo.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
@interface SNTApplicationCoreMetricsTest : XCTestCase
@property id mockConfigurator;
@property NSDictionary *extraMetricLabels;
@end
@implementation SNTApplicationCoreMetricsTest
- (void)setUp {
self.extraMetricLabels = @{@"service_name" : @"santa", @"corp_site" : @"roam"};
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
}
- (void)tearDown {
[self.mockConfigurator stopMocking];
}
- (NSDictionary *)fixUpDatesAndDataValuesOf:(NSDictionary *)exportedMetrics {
NSMutableDictionary *mutableMetrics =
[[SNTMetricFormatTestHelper convertDatesToFixedDateWithExportDict:exportedMetrics] mutableCopy];
// Ensure that we got data of the correct type but do not check the values as they'll change per
// call. Instead we stub out the values for testing.
NSMutableDictionary *metric = mutableMetrics[@"metrics"][@"/proc/birth_timestamp"];
XCTAssertTrue([metric[@"fields"][@""][0][@"data"] isKindOfClass:[NSNumber class]],
@"/proc/birth_timestamp data is not a number");
metric[@"fields"][@""][0][@"data"] = @1634148013203157;
mutableMetrics[@"metrics"][@"/proc/birth_timestamp"] = metric;
// Fix up CPU usage
metric = mutableMetrics[@"metrics"][@"/proc/cpu_usage"];
XCTAssertTrue([metric[@"fields"][@"mode"][0][@"data"] isKindOfClass:[@0.63002 class]],
@"/proc/cpu_usage has non-floating point data");
metric[@"fields"][@"mode"][0][@"data"] = @0.63002;
XCTAssertTrue([metric[@"fields"][@"mode"][1][@"data"] isKindOfClass:[@0.29522 class]],
@"/proc/cpu_usage has non-floating point data");
metric[@"fields"][@"mode"][1][@"data"] = @0.29522;
metric = mutableMetrics[@"metrics"][@"/proc/cpu_usage"];
// Fix up Memory (resident size)
metric = mutableMetrics[@"metrics"][@"/proc/memory/resident_size"];
XCTAssertTrue([metric[@"fields"][@""][0][@"data"] isKindOfClass:[@22097920 class]]);
metric[@"fields"][@""][0][@"data"] = @22097920;
mutableMetrics[@"metrics"][@"/proc/memory/resident_size"] = metric;
// Fix up Memory (virtual size)
metric = mutableMetrics[@"metrics"][@"/proc/memory/virtual_size"];
XCTAssertTrue([metric[@"fields"][@""][0][@"data"] isKindOfClass:[@35634683904 class]]);
metric[@"fields"][@""][0][@"data"] = @35634683904;
mutableMetrics[@"metrics"][@"/proc/memory/virtual_size"] = metric;
return mutableMetrics;
}
- (void)testRegisteringCoreMetrics {
OCMStub([self.mockConfigurator extraMetricLabels]).andReturn(self.extraMetricLabels);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
SNTRegisterCoreMetrics();
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
NSString *shortOSVersion = [SNTSystemInfo osVersion];
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
if (@available(macOS 10.13, *)) {
formatter.formatOptions =
NSISO8601DateFormatWithFractionalSeconds | NSISO8601DateFormatWithInternetDateTime;
}
NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"];
NSString *hostname = [NSProcessInfo processInfo].hostName;
NSDictionary *expected = @{
@"metrics" : @{
@"/build/label" : @{
@"description" : @"Version of the binary",
@"type" : @2,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : version,
@"last_updated" : fixedDate,
@"value" : @""
} ]
},
},
@"/proc/birth_timestamp" : @{
@"description" : @"Start time of Santad, in microseconds since epoch",
@"type" : @3,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @1634148013203157,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/proc/cpu_usage" : @{
@"description" : @"CPU time consumed by this process, in seconds",
@"type" : @8,
@"fields" : @{
@"mode" : @[
@{
@"created" : fixedDate,
@"data" : @0.63002,
@"last_updated" : fixedDate,
@"value" : @"user"
},
@{
@"created" : fixedDate,
@"data" : @0.29522,
@"last_updated" : fixedDate,
@"value" : @"system"
}
],
},
},
@"/proc/memory/resident_size" : @{
@"description" : @"The resident set size of this process",
@"type" : @7,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @22097920,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/proc/memory/virtual_size" : @{
@"description" : @"The virtual memory size of this process",
@"type" : @7,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @35634683904,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/proc/os/version" : @{
@"description" : @"Short operating System version",
@"type" : @2,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : shortOSVersion,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/santa/mode" : @{
@"description" : @"Santa's operating mode",
@"type" : @6,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @"lockdown",
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
},
@"root_labels" : @{
@"host_name" : hostname,
@"job_name" : @"santad",
@"service_name" : @"santa",
@"corp_site" : @"roam",
@"username" : [NSProcessInfo processInfo].userName
},
};
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *exportedMetrics = [self fixUpDatesAndDataValuesOf:[metricSet export]];
XCTAssertNotNil(exportedMetrics);
XCTAssertEqualObjects(expected[@"root_labels"], exportedMetrics[@"root_labels"],
@"root_labels are different");
for (NSString *metricName in expected[@"metrics"]) {
NSDictionary *other = exportedMetrics[@"metrics"][metricName];
XCTAssertNotNil(other, @"exported Metrics Missing %@", metricName);
XCTAssertEqualObjects(expected[@"metrics"][metricName], other,
@"%@ does not match expected values", metricName);
}
}
// Test that setting a new value for an existing rootLabel overwrites the labels value.
//
// Becareful modifiying this test as the singleton makes this order dependent.
//
- (void)testRootLabelReplacement {
self.extraMetricLabels = @{@"host_name" : @"santa-host"};
OCMStub([self.mockConfigurator extraMetricLabels]).andReturn(self.extraMetricLabels);
SNTRegisterCoreMetrics();
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *output = [metricSet export];
NSDictionary *expectedRootLabels = @{
@"host_name" : @"santa-host",
@"job_name" : @"santad",
@"service_name" : @"santa",
@"corp_site" : @"roam",
@"username" : [NSProcessInfo processInfo].userName
};
XCTAssertEqualObjects(expectedRootLabels, output[@"root_labels"],
@"failed to update host_name root label");
}
// Test that setting a rootLabel key to "" removes it from the root labels.
//
// Becareful modifiying this test as the singleton makes this order dependent.
//
- (void)testRootLabelRemoval {
self.extraMetricLabels = @{@"host_name" : @""};
OCMStub([self.mockConfigurator extraMetricLabels]).andReturn(self.extraMetricLabels);
SNTRegisterCoreMetrics();
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *output = [metricSet export];
NSDictionary *expectedRootLabels = @{
@"job_name" : @"santad",
@"service_name" : @"santa",
@"corp_site" : @"roam",
@"username" : [NSProcessInfo processInfo].userName,
};
XCTAssertEqualObjects(expectedRootLabels, output[@"root_labels"],
@"failed to remove only host_name root label");
}
@end

View File

@@ -69,10 +69,11 @@
XCTestExpectation *expectation =
[self expectationWithDescription:@"Wait for santa's Auth dispatch queue"];
__block ESResponse *got = nil;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_EXEC
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
NSString *binaryPath = [NSString pathWithComponents:@[ testPath, binaryName ]];
struct stat fileStat;
@@ -93,31 +94,16 @@
testPath, binaryName);
}
- (void)testBinaryRules {
- (void)testRules {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSDictionary *testCases = @{
@"badbinary" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"goodbinary" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"noop" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
};
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
]];
for (NSString *binary in testCases) {
[self checkBinaryExecution:binary
testPath:fullTestPath
wantResult:[testCases[binary] intValue]];
}
}
- (void)testCertRules {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSDictionary *testCases = @{
@"banned_teamid" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"banned_teamid_allowed_binary" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"badcert" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"goodcert" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"noop" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
};
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath

View File

@@ -23,8 +23,7 @@
// Designated initializer takes a SNTEventLog instance so that we can
// call saveDecisionDetails: to create a fake cached decision for transitive
// rule creation requests that are still pending.
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager
eventLog:(SNTEventLog *)eventLog;
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager;
// Whenever an executable file is closed or renamed whitelist the resulting file.
// We assume that we have already determined that the writing process was a compiler.

View File

@@ -27,17 +27,14 @@
@interface SNTCompilerController ()
@property id<SNTEventProvider> eventProvider;
@property SNTEventLog *eventLog;
@end
@implementation SNTCompilerController
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider
eventLog:(SNTEventLog *)eventLog {
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider {
self = [super init];
if (self) {
_eventProvider = eventProvider;
_eventLog = eventLog;
}
return self;
}
@@ -50,11 +47,11 @@
cd.decision = SNTEventStateAllowPendingTransitive;
cd.vnodeId = message.vnode_id;
cd.sha256 = @"pending";
[self.eventLog cacheDecision:cd];
[[SNTEventLog logger] cacheDecision:cd];
}
- (void)removeFakeDecision:(santa_message_t)message {
[self.eventLog forgetCachedDecisionForVnodeId:message.vnode_id];
[[SNTEventLog logger] forgetCachedDecisionForVnodeId:message.vnode_id];
}
// Assume that this method is called only when we already know that the writing process is a
@@ -71,20 +68,20 @@
// Check if there is an existing (non-transitive) rule for this file. We leave existing rules
// alone, so that a allowlist or blocklist rule can't be overwritten by a transitive one.
SNTRuleTable *ruleTable = [SNTDatabaseController ruleTable];
SNTRule *prevRule = [ruleTable ruleForBinarySHA256:fi.SHA256 certificateSHA256:nil];
SNTRule *prevRule = [ruleTable ruleForBinarySHA256:fi.SHA256 certificateSHA256:nil teamID:nil];
if (!prevRule || prevRule.state == SNTRuleStateAllowTransitive) {
// Construct a new transitive allowlist rule for the executable.
SNTRule *rule = [[SNTRule alloc] initWithShasum:fi.SHA256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:@""];
SNTRule *rule = [[SNTRule alloc] initWithIdentifier:fi.SHA256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:@""];
// Add the new rule to the rules database.
NSError *err;
if (![ruleTable addRules:@[ rule ] cleanSlate:NO error:&err]) {
LOGE(@"unable to add new transitive rule to database: %@", err.localizedDescription);
} else {
[self.eventLog
[[SNTEventLog logger]
writeLog:[NSString
stringWithFormat:@"action=ALLOWLIST|pid=%d|pidversion=%d|path=%s|sha256=%@",
message.pid, message.pidversion, target, fi.SHA256]];

View File

@@ -28,6 +28,5 @@
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager
notificationQueue:(SNTNotificationQueue *)notQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog;
syncdQueue:(SNTSyncdQueue *)syncdQueue;
@end

View File

@@ -20,6 +20,7 @@
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTStrengthify.h"
@@ -44,7 +45,6 @@ double watchdogRAMPeak = 0;
@interface SNTDaemonControlController ()
@property NSString *_syncXsrfToken;
@property SNTPolicyProcessor *policyProcessor;
@property SNTEventLog *eventLog;
@property id<SNTEventProvider> eventProvider;
@property SNTNotificationQueue *notQueue;
@property SNTSyncdQueue *syncdQueue;
@@ -54,8 +54,7 @@ double watchdogRAMPeak = 0;
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider
notificationQueue:(SNTNotificationQueue *)notQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog {
syncdQueue:(SNTSyncdQueue *)syncdQueue {
self = [super init];
if (self) {
_policyProcessor =
@@ -63,7 +62,6 @@ double watchdogRAMPeak = 0;
_eventProvider = eventProvider;
_notQueue = notQueue;
_syncdQueue = syncdQueue;
_eventLog = eventLog;
}
return self;
}
@@ -94,10 +92,10 @@ double watchdogRAMPeak = 0;
#pragma mark Database ops
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive))reply {
int64_t transitive, int64_t teamID))reply {
SNTRuleTable *rdb = [SNTDatabaseController ruleTable];
reply([rdb binaryRuleCount], [rdb certificateRuleCount], [rdb compilerRuleCount],
[rdb transitiveRuleCount]);
[rdb transitiveRuleCount], [rdb teamIDRuleCount]);
}
- (void)databaseRuleAddRules:(NSArray *)rules
@@ -139,9 +137,11 @@ double watchdogRAMPeak = 0;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] ruleForBinarySHA256:binarySHA256
certificateSHA256:certificateSHA256]);
certificateSHA256:certificateSHA256
teamID:teamID]);
}
#pragma mark Decision Ops
@@ -149,10 +149,12 @@ double watchdogRAMPeak = 0;
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTEventState))reply {
reply([self.policyProcessor decisionForFilePath:filePath
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256]
certificateSHA256:certificateSHA256
teamID:teamID]
.decision);
}
@@ -223,6 +225,15 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply {
[[SNTConfigurator configurator] setBlockUSBMount:enabled];
reply();
}
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply {
[[SNTConfigurator configurator] setRemountUSBMode:remountUSBMode];
reply();
}
- (void)enableBundles:(void (^)(BOOL))reply {
reply([SNTConfigurator configurator].enableBundles);
}
@@ -241,6 +252,13 @@ double watchdogRAMPeak = 0;
reply();
}
#pragma mark Metrics Ops
- (void)metrics:(void (^)(NSDictionary *))reply {
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
reply([metricSet export]);
}
#pragma mark GUI Ops
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener {
@@ -301,7 +319,7 @@ double watchdogRAMPeak = 0;
[eventTable addStoredEvent:event];
// Log all of the generated bundle events.
[self.eventLog logBundleHashingEvents:events];
[[SNTEventLog logger] logBundleHashingEvents:events];
WEAKIFY(self);

View File

@@ -18,6 +18,23 @@
#include "Source/common/SNTKernelCommon.h"
#include "Source/santad/EventProviders/SNTEventProvider.h"
const static NSString *kBlockBinary = @"BlockBinary";
const static NSString *kAllowBinary = @"AllowBinary";
const static NSString *kBlockCertificate = @"BlockCertificate";
const static NSString *kAllowCertificate = @"AllowCertificate";
const static NSString *kBlockTeamID = @"BlockTeamID";
const static NSString *kAllowTeamID = @"AllowTeamID";
const static NSString *kBlockScope = @"BlockScope";
const static NSString *kAllowScope = @"AllowScope";
const static NSString *kAllowUnknown = @"AllowUnknown";
const static NSString *kBlockUnknown = @"BlockUnknown";
const static NSString *kAllowCompiler = @"AllowCompiler";
const static NSString *kAllowTransitive = @"AllowTransitive";
const static NSString *kUnknownEventState = @"Unknown";
const static NSString *kBlockPrinterWorkaround = @"BlockPrinterWorkaround";
const static NSString *kAllowNoFileInfo = @"AllowNoFileInfo";
const static NSString *kAllowNullVNode = @"AllowNullVNode";
@class MOLCodesignChecker;
@class SNTDriverManager;
@class SNTEventLog;
@@ -40,8 +57,7 @@
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
notifierQueue:(SNTNotificationQueue *)notifierQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog;
syncdQueue:(SNTSyncdQueue *)syncdQueue;
///
/// Handles the logic of deciding whether to allow the binary to run or not, sends the response to

View File

@@ -19,6 +19,7 @@
#include <utmpx.h>
#include "Source/common/SNTLogging.h"
#include "Source/common/SNTMetricSet.h"
#import <MOLCodesignChecker/MOLCodesignChecker.h>
@@ -46,26 +47,33 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
@interface SNTExecutionController ()
@property id<SNTEventProvider> eventProvider;
@property SNTEventLog *eventLog;
@property SNTEventTable *eventTable;
@property SNTNotificationQueue *notifierQueue;
@property SNTPolicyProcessor *policyProcessor;
@property SNTRuleTable *ruleTable;
@property SNTSyncdQueue *syncdQueue;
@property SNTMetricCounter *events;
@property dispatch_queue_t eventQueue;
@end
@implementation SNTExecutionController
static NSString *const kPrinterProxyPreMonterey =
(@"/System/Library/Frameworks/Carbon.framework/Versions/Current/"
@"Frameworks/Print.framework/Versions/Current/Plugins/PrinterProxy.app/"
@"Contents/MacOS/PrinterProxy");
static NSString *const kPrinterProxyPostMonterey =
(@"/System/Library/PrivateFrameworks/PrintingPrivate.framework/"
@"Versions/Current/Plugins/PrinterProxy.app/Contents/MacOS/PrinterProxy");
#pragma mark Initializers
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
notifierQueue:(SNTNotificationQueue *)notifierQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog {
syncdQueue:(SNTSyncdQueue *)syncdQueue {
self = [super init];
if (self) {
_eventProvider = eventProvider;
@@ -73,7 +81,6 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
_eventTable = eventTable;
_notifierQueue = notifierQueue;
_syncdQueue = syncdQueue;
_eventLog = eventLog;
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:_ruleTable];
_eventQueue = dispatch_queue_create("com.google.santad.event_upload", DISPATCH_QUEUE_SERIAL);
@@ -81,10 +88,37 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// This establishes the XPC connection between libsecurity and syspolicyd.
// Not doing this causes a deadlock as establishing this link goes through xpcproxy.
(void)[[MOLCodesignChecker alloc] initWithSelf];
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
_events = [metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"action_response" ]
helpText:@"Events processed by Santa per response"];
}
return self;
}
- (void)incrementEventCounters:(SNTEventState)eventType {
const NSString *eventTypeStr;
switch (eventType) {
case SNTEventStateBlockBinary: eventTypeStr = kBlockBinary; break;
case SNTEventStateAllowBinary: eventTypeStr = kAllowBinary; break;
case SNTEventStateBlockCertificate: eventTypeStr = kBlockCertificate; break;
case SNTEventStateAllowCertificate: eventTypeStr = kAllowCertificate; break;
case SNTEventStateBlockTeamID: eventTypeStr = kBlockTeamID; break;
case SNTEventStateAllowTeamID: eventTypeStr = kAllowTeamID; break;
case SNTEventStateBlockScope: eventTypeStr = kBlockScope; break;
case SNTEventStateAllowScope: eventTypeStr = kAllowScope; break;
case SNTEventStateBlockUnknown: eventTypeStr = kBlockUnknown; break;
case SNTEventStateAllowUnknown: eventTypeStr = kAllowUnknown; break;
case SNTEventStateAllowCompiler: eventTypeStr = kAllowCompiler; break;
case SNTEventStateAllowTransitive: eventTypeStr = kAllowTransitive; break;
default: eventTypeStr = kUnknownEventState; break;
}
[_events incrementForFieldValues:@[ (NSString *)eventTypeStr ]];
}
#pragma mark Binary Validation
- (void)validateBinaryWithMessage:(santa_message_t)message {
@@ -92,19 +126,23 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
if (unlikely(message.path == NULL)) {
LOGE(@"Path for vnode_id is NULL: %llu/%llu", message.vnode_id.fsid, message.vnode_id.fileid);
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kAllowNullVNode ]];
return;
}
NSError *fileInfoError;
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
if (unlikely(!binInfo)) {
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kAllowNoFileInfo ]];
return;
}
// PrinterProxy workaround, see description above the method for more details.
if ([self printerProxyWorkaround:binInfo]) {
[self.eventProvider postAction:ACTION_RESPOND_DENY forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kBlockPrinterWorkaround ]];
return;
}
@@ -113,6 +151,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
LOGD(@"%@ is larger than %zu. Letting santa-driver know we are working on it.", binInfo.path,
kLargeBinarySize);
[self.eventProvider postAction:ACTION_RESPOND_ACK forMessage:message];
// TODO(markowsky): Maybe add a metric here for how many large executables we're seeing.
}
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo];
@@ -127,7 +166,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// ACTION_NOTIFY_EXEC message related to the transitive rule is received.
NSString *ttyPath;
if (action == ACTION_RESPOND_ALLOW) {
[_eventLog cacheDecision:cd];
[[SNTEventLog logger] cacheDecision:cd];
} else {
ttyPath = [self ttyPathForPID:message.ppid];
}
@@ -141,10 +180,13 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// Send the decision to the kernel.
[self.eventProvider postAction:action forMessage:message];
// Increment counters;
[self incrementEventCounters:cd.decision];
// Log to database if necessary.
if (cd.decision != SNTEventStateAllowBinary && cd.decision != SNTEventStateAllowCompiler &&
cd.decision != SNTEventStateAllowTransitive && cd.decision != SNTEventStateAllowCertificate &&
cd.decision != SNTEventStateAllowScope) {
cd.decision != SNTEventStateAllowTeamID && cd.decision != SNTEventStateAllowScope) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.occurrenceDate = [[NSDate alloc] init];
se.fileSHA256 = cd.sha256;
@@ -181,13 +223,16 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
se.quarantineTimestamp = binInfo.quarantineTimestamp;
se.quarantineAgentBundleID = binInfo.quarantineAgentBundleID;
dispatch_async(_eventQueue, ^{
[self.eventTable addStoredEvent:se];
});
// Only store events if there is a sync server configured.
if ([SNTConfigurator configurator].syncBaseURL) {
dispatch_async(_eventQueue, ^{
[self.eventTable addStoredEvent:se];
});
}
// If binary was blocked, do the needful
if (action != ACTION_RESPOND_ALLOW && action != ACTION_RESPOND_ALLOW_COMPILER) {
[_eventLog logDeniedExecution:cd withMessage:message];
[[SNTEventLog logger] logDeniedExecution:cd withMessage:message];
if ([[SNTConfigurator configurator] enableBundles] && binInfo.bundle) {
// If the binary is part of a bundle, find and hash all the related binaries in the bundle.
@@ -245,13 +290,10 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
- (BOOL)printerProxyWorkaround:(SNTFileInfo *)fi {
if ([fi.path hasSuffix:@"/Contents/MacOS/PrinterProxy"] &&
[fi.path containsString:@"Library/Printers"]) {
NSString *proxyPath = (@"/System/Library/Frameworks/Carbon.framework/Versions/Current/"
@"Frameworks/Print.framework/Versions/Current/Plugins/PrinterProxy.app/"
@"Contents/MacOS/PrinterProxy");
SNTFileInfo *proxyFi = [[SNTFileInfo alloc] initWithPath:proxyPath];
SNTFileInfo *proxyFi = [self printerProxyFileInfo];
if ([proxyFi.SHA256 isEqual:fi.SHA256]) return NO;
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyPath];
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyFi.path];
NSFileHandle *outFh = [NSFileHandle fileHandleForWritingAtPath:fi.path];
[outFh writeData:[inFh readDataToEndOfFile]];
[inFh closeFile];
@@ -266,6 +308,15 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
return NO;
}
/**
Returns an SNTFileInfo for the system PrinterProxy path on this system.
*/
- (SNTFileInfo *)printerProxyFileInfo {
SNTFileInfo *proxyInfo = [[SNTFileInfo alloc] initWithPath:kPrinterProxyPostMonterey];
if (!proxyInfo) proxyInfo = [[SNTFileInfo alloc] initWithPath:kPrinterProxyPreMonterey];
return proxyInfo;
}
- (NSString *)ttyPathForPID:(pid_t)pid {
if (pid < 2) return nil; // don't bother even looking for launchd.

View File

@@ -21,6 +21,7 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTFileInfo.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTRule.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
@@ -52,6 +53,8 @@
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
NSURL *url = [NSURL URLWithString:@"https://localhost/test"];
OCMStub([self.mockConfigurator syncBaseURL]).andReturn(url);
self.mockDriverManager = OCMClassMock([SNTDriverManager class]);
@@ -69,8 +72,7 @@
ruleTable:self.mockRuleDatabase
eventTable:self.mockEventDatabase
notifierQueue:nil
syncdQueue:nil
eventLog:nil];
syncdQueue:nil];
}
/// Return a pre-configured santa_message_ t for testing with.
@@ -87,14 +89,23 @@
return (santa_vnode_id_t){.fsid = 1234, .fileid = 5678};
}
- (void)tearDown {
[self.mockFileInfo stopMocking];
[self.mockCodesignChecker stopMocking];
[self.mockDriverManager stopMocking];
[self.mockRuleDatabase stopMocking];
[self.mockEventDatabase stopMocking];
- (void)checkMetricCounters:(const NSString *)expectedFieldValueName
expected:(NSNumber *)expectedValue {
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *eventCounter = [metricSet export][@"metrics"][@"/santa/events"];
BOOL foundField;
for (NSDictionary *fieldValue in eventCounter[@"fields"][@"action_response"]) {
if ([expectedFieldValueName isEqualToString:fieldValue[@"value"]]) {
XCTAssertEqualObjects(expectedValue, fieldValue[@"data"],
@"%@ counter does not match expected value", expectedFieldValueName);
foundField = YES;
break;
}
}
[super tearDown];
if (!foundField) {
XCTFail(@"failed to find %@ field value", expectedFieldValueName);
}
}
- (void)testBinaryAllowRule {
@@ -104,11 +115,13 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllow;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:@"AllowBinary" expected:@2];
}
- (void)testBinaryBlockRule {
@@ -118,11 +131,15 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateBlock;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
// verify that we're incrementing the binary block
[self checkMetricCounters:@"BlockBinary" expected:@1];
}
- (void)testCertificateAllowRule {
@@ -135,11 +152,13 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllow;
rule.type = SNTRuleTypeCertificate;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowCertificate expected:@1];
}
- (void)testCertificateBlockRule {
@@ -152,11 +171,16 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateBlock;
rule.type = SNTRuleTypeCertificate;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil])
.andReturn(rule);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
[self checkMetricCounters:@"BlockCertificate" expected:@1];
}
- (void)testBinaryAllowCompilerRule {
@@ -167,12 +191,14 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW_COMPILER
forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowCompiler expected:@1];
}
- (void)testBinaryAllowCompilerRuleDisabled {
@@ -183,11 +209,13 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowBinary expected:@1];
}
- (void)testBinaryAllowTransitiveRule {
@@ -198,11 +226,14 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:@"AllowBinary" expected:@2];
}
- (void)testBinaryAllowTransitiveRuleDisabled {
@@ -214,11 +245,17 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
[self checkMetricCounters:kAllowBinary expected:@2];
[self checkMetricCounters:kAllowTransitive expected:@1];
}
- (void)testDefaultDecision {
@@ -226,33 +263,42 @@
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
}
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
- (void)testOutOfScope {
OCMStub([self.mockFileInfo isMachO]).andReturn(NO);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kBlockUnknown expected:@2];
[self checkMetricCounters:kAllowUnknown expected:@1];
}
- (void)testMissingShasum {
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowScope expected:@1];
}
- (void)testOutOfScope {
OCMStub([self.mockFileInfo isMachO]).andReturn(NO);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowScope expected:@2];
}
- (void)testPageZero {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo isMissingPageZero]).andReturn(YES);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
[self checkMetricCounters:kBlockUnknown expected:@3];
}
@end

View File

@@ -44,7 +44,8 @@
///
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256;
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID;
/// Convenience initializer with nil hashes for both the file and certificate.
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo;
@@ -57,6 +58,7 @@
///
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256;
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID;
@end

View File

@@ -40,9 +40,11 @@
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256 {
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
cd.teamID = teamID;
// If the binary is a critical system binary, don't check its signature.
// The binary was validated at startup when the rule table was initialized.
@@ -64,11 +66,15 @@
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
cd.certChain = csInfo.certificates;
cd.teamID = teamID ?: [csInfo.signingInformation valueForKey:@"teamid"];
teamID = cd.teamID;
}
}
cd.quarantineURL = fileInfo.quarantineDataURL;
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256 certificateSHA256:cd.certSHA256];
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256
certificateSHA256:cd.certSHA256
teamID:teamID];
if (rule) {
switch (rule.type) {
case SNTRuleTypeBinary:
@@ -115,6 +121,20 @@
default: break;
}
break;
case SNTRuleTypeTeamID:
switch (rule.state) {
case SNTRuleStateAllow: cd.decision = SNTEventStateAllowTeamID; return cd;
case SNTRuleStateSilentBlock:
cd.silentBlock = YES;
// intentional fallthrough
case SNTRuleStateBlock:
cd.customMsg = rule.customMsg;
cd.decision = SNTEventStateBlockTeamID;
return cd;
default: break;
}
break;
default: break;
}
}
@@ -149,19 +169,22 @@
}
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo {
return [self decisionForFileInfo:fileInfo fileSHA256:nil certificateSHA256:nil];
return [self decisionForFileInfo:fileInfo fileSHA256:nil certificateSHA256:nil teamID:nil];
}
// Used by `$ santactl fileinfo`.
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256 {
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID {
SNTFileInfo *fileInfo;
NSError *error;
fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
return [self decisionForFileInfo:fileInfo
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256];
certificateSHA256:certificateSHA256
teamID:teamID];
}
///

View File

@@ -2,11 +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>$(TeamIdentifierPrefix)com.google.santa.daemon</string>
<key>com.apple.developer.team-identifier</key>
<string>EQHXZ8M8AV</string>
<key>com.apple.application-identifier</key>
<string>EQHXZ8M8AV.com.google.santa.daemon</string>
<key>com.apple.developer.endpoint-security.client</key>
<true/>
<key>com.apple.developer.team-identifier</key>
<string>EQHXZ8M8AV</string>
<key>keychain-access-groups</key>
<array>
<string>EQHXZ8M8AV.com.google.santa.daemon</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
#include <stdio.h>
int main(int argc, char* argv[]) {
printf("This binary should fail to execute because of its banned team ID!");
return 0;
}

Binary file not shown.

View File

@@ -0,0 +1,8 @@
#include <stdio.h>
int main(int argc, char* argv[]) {
printf(
"Hello world, this binary's execution is okay even though its teamID "
"isn't");
return 0;
}

Binary file not shown.

Binary file not shown.

View File

@@ -14,11 +14,14 @@ objc_library(
],
deps = [
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/santametricservice/Formats:SNTMetricMonarchJSONFormat",
"//Source/santametricservice/Formats:SNTMetricRawJSONFormat",
"//Source/santametricservice/Writers:SNTMetricFileWriter",
"//Source/santametricservice/Writers:SNTMetricHTTPWriter",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
@@ -27,6 +30,7 @@ objc_library(
santa_unit_test(
name = "SNTMetricServiceTest",
srcs = ["SNTMetricServiceTest.m"],
structured_resources = ["//Source/santametricservice/Formats:testdata"],
deps = [
":SNTMetricServiceLib",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
@@ -38,8 +42,8 @@ test_suite(
name = "unit_tests",
tests = [
":SNTMetricServiceTest",
"//Source/santametricservice/Formats:SNTMetricRawJSONFormatTest",
"//Source/santametricservice/Writers:SNTMetricFileWriterTest",
"//Source/santametricservice/Formats:format_tests",
"//Source/santametricservice/Writers:writer_tests",
],
)
@@ -53,7 +57,13 @@ macos_command_line_application(
],
infoplists = ["Info.plist"],
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":SNTMetricServiceLib"],
deps = [
":SNTMetricServiceLib",
],
)

View File

@@ -33,6 +33,20 @@ objc_library(
],
)
objc_library(
name = "SNTMetricMonarchJSONFormat",
srcs = [
"SNTMetricFormat.h",
"SNTMetricMonarchJSONFormat.h",
"SNTMetricMonarchJSONFormat.m",
],
deps = [
":SNTMetricFormat",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
],
)
santa_unit_test(
name = "SNTMetricRawJSONFormatTest",
srcs = [
@@ -45,9 +59,27 @@ santa_unit_test(
],
)
santa_unit_test(
name = "SNTMetricMonarchJSONFormatTest",
srcs = [
"SNTMetricMonarchJSONFormatTest.m",
],
structured_resources = [":testdata"],
deps = [
":SNTMetricFormatTestHelper",
":SNTMetricMonarchJSONFormat",
],
)
filegroup(
name = "testdata",
srcs = glob(["testdata/**"]),
)
test_suite(
name = "format_tests",
tests = [
":SNTMetricMonarchJSONFormatTest",
":SNTMetricRawJSONFormatTest",
],
)

View File

@@ -15,5 +15,6 @@
#import <Foundation/Foundation.h>
@interface SNTMetricFormatTestHelper : NSObject
+ (NSDictionary *)convertDatesToFixedDateWithExportDict:(NSDictionary *)exportDict;
+ (NSDictionary *)createValidMetricsDictionary;
@end

View File

@@ -19,8 +19,22 @@
@implementation SNTMetricFormatTestHelper
+ (NSDictionary *)convertDatesToFixedDateWithExportDict:(NSMutableDictionary *)exportDict {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
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.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
localFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
localFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
formatter = localFormatter;
}
NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"];
for (NSString *metricName in exportDict[@"metrics"]) {
@@ -45,26 +59,26 @@
// Add constants
[metricSet addConstantStringWithName:@"/build/label"
helpText:@"Software version running."
helpText:@"Software version running"
value:@"20210809.0.1"];
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
helpText:@"Is santad using the endpoint security framework."
helpText:@"Is santad using the endpoint security framework"
value:YES];
[metricSet addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this LogDumper instance, in microseconds "
@"since epoch"
value:(long long)(0x12345668910)];
[metricSet
addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this santad instance, in microseconds since epoch"
value:(long long)(0x12345668910)];
// Add Metrics
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"rule_type" ]
helpText:@"Count of events on the host"];
helpText:@"Count of process exec 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."];
helpText:@"Number of rules"];
[g set:1 forFieldValues:@[ @"binary" ]];
[g set:3 forFieldValues:@[ @"certificate" ]];
@@ -73,12 +87,12 @@
SNTMetricInt64Gauge *virtualMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
fieldNames:@[]
helpText:@"The virtual memory size of this process."];
helpText:@"The virtual memory size of this process"];
SNTMetricInt64Gauge *residentMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
fieldNames:@[]
helpText:@"The resident set siz of this process."];
helpText:@"The resident set size of this process"];
[metricSet registerCallback:^(void) {
[virtualMemoryGauge set:987654321 forFieldValues:@[]];

View File

@@ -0,0 +1,20 @@
/// 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/santametricservice/Formats/SNTMetricFormat.h"
@interface SNTMetricMonarchJSONFormat : NSObject <SNTMetricFormat>
@end

View File

@@ -0,0 +1,226 @@
/// 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/santametricservice/Formats/SNTMetricMonarchJSONFormat.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
const NSString *kMetricsCollection = @"metricsCollection";
const NSString *kMetricsDataSet = @"metricsDataSet";
const NSString *kMetricName = @"metricName";
const NSString *kStreamKind = @"streamKind";
const NSString *kValueType = @"valueType";
const NSString *kDescription = @"description";
const NSString *kData = @"data";
const NSString *kFieldDescriptor = @"fieldDescriptor";
const NSString *kBoolValue = @"boolValue";
const NSString *kBoolValueType = @"BOOL";
const NSString *kInt64Value = @"int64Value";
const NSString *kInt64ValueType = @"INT64";
const NSString *kStringValue = @"stringValue";
const NSString *kStringValueType = @"STRING";
const NSString *kName = @"name";
const NSString *kStartTimestamp = @"startTimestamp";
const NSString *kEndTimestamp = @"endTimestamp";
const NSString *kRootLabels = @"rootLabels";
const NSString *kKey = @"key";
@implementation SNTMetricMonarchJSONFormat {
NSISO8601DateFormatter *_dateFormatter;
}
- (instancetype)init {
self = [super init];
if (self) {
_dateFormatter = [[NSISO8601DateFormatter alloc] init];
_dateFormatter.formatOptions = NSISO8601DateFormatWithInternetDateTime;
if (@available(macOS 10.13, *)) {
_dateFormatter.formatOptions |= NSISO8601DateFormatWithFractionalSeconds;
}
}
return self;
}
- (void)encodeValueAndStreamKindFor:(NSString *)metricName
withMetric:(NSDictionary *)metric
into:(NSMutableDictionary *)monarchMetric {
if (!metric[@"type"]) {
LOGE(@"metric type not supposed to be nil for %@", metricName);
return;
}
NSNumber *type = metric[@"type"];
if (![type isKindOfClass:[NSNumber class]]) {
LOGE(@"%@ [@\"type\"] is not a number", metricName);
return;
}
switch ((SNTMetricType)[type intValue]) {
case SNTMetricTypeConstantBool: monarchMetric[kValueType] = kBoolValueType; break;
case SNTMetricTypeConstantString: monarchMetric[kValueType] = kStringValueType; break;
case SNTMetricTypeConstantInt64: monarchMetric[kValueType] = kInt64ValueType; break;
case SNTMetricTypeConstantDouble: monarchMetric[kValueType] = @"DOUBLE"; break;
case SNTMetricTypeGaugeBool:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = kBoolValueType;
break;
case SNTMetricTypeGaugeString:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = kStringValueType;
break;
case SNTMetricTypeGaugeInt64:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = kInt64ValueType;
break;
case SNTMetricTypeGaugeDouble:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = @"DOUBLE";
break;
case SNTMetricTypeCounter:
monarchMetric[kStreamKind] = @"CUMULATIVE";
monarchMetric[kValueType] = kInt64ValueType;
break;
default:
LOGE(@"encountered unknown SNTMetricType - %ld for %@", (SNTMetricType)metric[@"type"],
metricName);
break;
}
}
- (NSArray<NSDictionary *> *)encodeDataForMetric:(NSDictionary *)metric {
NSMutableArray<NSDictionary *> *monarchMetricData = [[NSMutableArray alloc] init];
for (NSString *fieldName in metric[@"fields"]) {
for (NSDictionary *entry in metric[@"fields"][fieldName]) {
NSMutableDictionary *monarchDataEntry = [[NSMutableDictionary alloc] init];
if (![fieldName isEqualToString:@""]) {
monarchDataEntry[@"field"] = @[ @{kName : fieldName, kStringValue : entry[@"value"]} ];
}
monarchDataEntry[kStartTimestamp] = [self->_dateFormatter stringFromDate:entry[@"created"]];
monarchDataEntry[kEndTimestamp] =
[self->_dateFormatter stringFromDate:entry[@"last_updated"]];
if (!metric[@"type"]) {
LOGE(@"metric type is nil");
continue;
}
NSNumber *type = metric[@"type"];
switch ((SNTMetricType)[type intValue]) {
case SNTMetricTypeConstantBool:
case SNTMetricTypeGaugeBool: monarchDataEntry[kBoolValue] = entry[@"data"]; break;
case SNTMetricTypeConstantInt64:
case SNTMetricTypeGaugeInt64:
case SNTMetricTypeCounter: monarchDataEntry[kInt64Value] = entry[@"data"]; break;
case SNTMetricTypeConstantDouble:
case SNTMetricTypeGaugeDouble: monarchDataEntry[@"doubleValue"] = entry[@"data"]; break;
case SNTMetricTypeConstantString:
case SNTMetricTypeGaugeString: monarchDataEntry[kStringValue] = entry[@"data"]; break;
default: LOGE(@"encountered unknown SNTMetricType %ld", [type longValue]); break;
}
[monarchMetricData addObject:monarchDataEntry];
}
}
return monarchMetricData;
}
/*
* Translates SNTMetricSet fields to monarch's expected format. In this implementation only string
* type fields are supported.
*/
- (NSArray<NSDictionary *> *)encodeFieldsFor:(NSDictionary *)metric {
NSMutableArray<NSDictionary *> *monarchFields = [[NSMutableArray alloc] init];
for (NSString *fieldName in metric[@"fields"]) {
if (![fieldName isEqualToString:@""]) {
[monarchFields addObject:@{kName : fieldName, @"fieldType" : kStringValueType}];
}
}
return monarchFields;
}
/**
* formatMetric translates the SNTMetricSet metric entries into those consumable by Monarch.
**/
- (NSDictionary *)formatMetric:(NSString *)name withMetric:(NSDictionary *)metric {
NSMutableDictionary *monarchMetric = [[NSMutableDictionary alloc] init];
monarchMetric[kMetricName] = name;
if (metric[kDescription]) {
monarchMetric[kDescription] = metric[kDescription];
}
NSArray<NSDictionary *> *fieldDescriptorEntries = [self encodeFieldsFor:metric];
if (fieldDescriptorEntries.count > 0) {
monarchMetric[kFieldDescriptor] = [self encodeFieldsFor:metric];
}
[self encodeValueAndStreamKindFor:name withMetric:metric into:monarchMetric];
monarchMetric[@"data"] = [self encodeDataForMetric:metric];
return monarchMetric;
}
/**
* Normalizes the metrics dictionary for exporting to JSON
**/
- (NSDictionary *)normalize:(NSDictionary *)metrics {
NSMutableArray<NSDictionary *> *monarchMetrics = [[NSMutableArray alloc] init];
for (NSString *metricName in metrics[@"metrics"]) {
[monarchMetrics addObject:[self formatMetric:metricName
withMetric:metrics[@"metrics"][metricName]]];
}
NSMutableArray<NSDictionary *> *rootLabels = [[NSMutableArray alloc] init];
for (NSString *keyName in metrics[@"root_labels"]) {
[rootLabels addObject:@{kKey : keyName, kStringValue : metrics[@"root_labels"][keyName]}];
}
return @{kMetricsCollection : @[ @{kMetricsDataSet : monarchMetrics, kRootLabels : rootLabels} ]};
}
/*
* Convert normalizes and converts the metrics dictionary to a JSON
* object consumable by parts of Google Monarch's tooling.
*
* @param metrics an NSDictionary exported by the SNTMetricSet
* @param error a pointer to an NSError to allow errors to bubble up.
*
* Returns an NSArray containing one entry of all metrics serialized to JSON or
* nil on error.
*/
- (NSArray<NSData *> *)convert:(NSDictionary *)metrics error:(NSError **)err {
NSDictionary *normalizedMetrics = [self normalize:metrics];
NSData *json = [NSJSONSerialization dataWithJSONObject:normalizedMetrics
options:NSJSONWritingPrettyPrinted
error:err];
if (json == nil || (err != nil && *err != nil)) {
return nil;
}
return @[ json ];
}
@end

View File

@@ -0,0 +1,50 @@
#import <XCTest/XCTest.h>
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
#import "Source/santametricservice/Formats/SNTMetricMonarchJSONFormat.h"
@interface SNTMetricMonarchJSONFormatTest : XCTestCase
@end
@implementation SNTMetricMonarchJSONFormatTest
- (void)testMetricsConversionToJSON {
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
SNTMetricMonarchJSONFormat *formatter = [[SNTMetricMonarchJSONFormat alloc] init];
NSError *err = nil;
NSArray<NSData *> *output = [formatter convert:validMetricsDict error:&err];
XCTAssertEqual(1, output.count);
XCTAssertNotNil(output[0]);
XCTAssertNil(err);
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:output[0]
options:NSJSONReadingAllowFragments
error:&err];
XCTAssertNotNil(jsonDict);
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"testdata/json/monarch.json"];
NSData *goldenFileData = [NSData dataWithContentsOfFile:path];
XCTAssertNotNil(goldenFileData, @"unable to open / read golden file");
NSDictionary *expectedJSONDict =
[NSJSONSerialization JSONObjectWithData:goldenFileData
options:NSJSONReadingAllowFragments
error:&err];
XCTAssertNotNil(expectedJSONDict);
XCTAssertEqualObjects(expectedJSONDict, jsonDict, @"generated JSON does not match golden file.");
}
- (void)testPassingANilOrNullErrorDoesNotCrash {
SNTMetricMonarchJSONFormat *formatter = [[SNTMetricMonarchJSONFormat alloc] init];
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
[formatter convert:validMetricsDict error:nil];
[formatter convert:validMetricsDict error:NULL];
}
@end

View File

@@ -23,7 +23,9 @@
self = [super init];
if (self) {
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
_dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
_dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
}
return self;
}
@@ -81,7 +83,7 @@
if (![NSJSONSerialization isValidJSONObject:normalizedMetrics]) {
if (err != nil) {
*err = [[NSError alloc]
initWithDomain:@"SNTMetricRawJSONFileWriter"
initWithDomain:@"com.google.santa.metricservice.formats.rawjson"
code:EINVAL
userInfo:@{
NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics"

View File

@@ -43,8 +43,8 @@
SNTMetricRawJSONFormat *formatter = [[SNTMetricRawJSONFormat alloc] init];
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
NSArray<NSData *> *output = [formatter convert:validMetricsDict error:nil];
output = [formatter convert:validMetricsDict error:NULL];
[formatter convert:validMetricsDict error:nil];
[formatter convert:validMetricsDict error:NULL];
}
@end

View File

@@ -0,0 +1,152 @@
{
"metricsCollection" : [
{
"rootLabels" : [
{
"key" : "hostname",
"stringValue" : "testHost"
},
{
"key" : "username",
"stringValue" : "testUser"
}
],
"metricsDataSet" : [
{
"metricName" : "/santa/rules",
"description" : "Number of rules",
"valueType" : "INT64",
"fieldDescriptor" : [
{
"name" : "rule_type",
"fieldType" : "STRING"
}
],
"streamKind" : "GAUGE",
"data" : [
{
"int64Value" : 1,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "binary"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
},
{
"int64Value" : 3,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "certificate"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
},
{
"valueType" : "INT64",
"streamKind" : "GAUGE",
"data" : [
{
"int64Value" : 123456789,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
],
"metricName" : "/proc/memory/resident_size",
"description" : "The resident set size of this process"
},
{
"valueType" : "INT64",
"fieldDescriptor" : [
{
"name" : "rule_type",
"fieldType" : "STRING"
}
],
"streamKind" : "CUMULATIVE",
"data" : [
{
"int64Value" : 1,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "binary"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
},
{
"int64Value" : 2,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "certificate"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
],
"metricName" : "/santa/events",
"description" : "Count of process exec events on the host"
},
{
"metricName" : "/santa/using_endpoint_security_framework",
"description" : "Is santad using the endpoint security framework",
"valueType" : "BOOL",
"data" : [
{
"boolValue" : true,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
},
{
"metricName" : "/proc/birth_timestamp",
"description" : "Start time of this santad instance, in microseconds since epoch",
"valueType" : "INT64",
"data" : [
{
"int64Value" : 1250999830800,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
},
{
"valueType" : "INT64",
"streamKind" : "GAUGE",
"data" : [
{
"int64Value" : 987654321,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
],
"metricName" : "/proc/memory/virtual_size",
"description" : "The virtual memory size of this process"
},
{
"metricName" : "/build/label",
"valueType" : "STRING",
"description" : "Software version running",
"data" : [
{
"stringValue" : "20210809.0.1",
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
}
]
}
]
}

View File

@@ -2,6 +2,7 @@
"metrics" : {
"/santa/rules" : {
"type" : 7,
"description" : "Number of rules",
"fields" : {
"rule_type" : [
{
@@ -21,6 +22,7 @@
},
"/santa/events" : {
"type" : 9,
"description" : "Count of process exec events on the host",
"fields" : {
"rule_type" : [
{
@@ -40,6 +42,7 @@
},
"/proc/memory/resident_size" : {
"type" : 7,
"description" : "The resident set size of this process",
"fields" : {
"" : [
{
@@ -53,6 +56,7 @@
},
"/build/label" : {
"type" : 2,
"description" : "Software version running",
"fields" : {
"" : [
{
@@ -66,6 +70,7 @@
},
"/proc/birth_timestamp" : {
"type" : 3,
"description" : "Start time of this santad instance, in microseconds since epoch",
"fields" : {
"" : [
{
@@ -79,6 +84,7 @@
},
"/proc/memory/virtual_size" : {
"type" : 7,
"description" : "The virtual memory size of this process",
"fields" : {
"" : [
{
@@ -92,6 +98,7 @@
},
"/santa/using_endpoint_security_framework" : {
"type" : 1,
"description" : "Is santad using the endpoint security framework",
"fields" : {
"" : [
{

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