Compare commits

...

87 Commits
2024.3 ... main

Author SHA1 Message Date
Günther Noack
261425aa64 docs: Colorize callout and make the link work (#1445) 2025-02-05 14:23:36 +01:00
Günther Noack
c17c890356 docs: Add deprecation note (#1444) 2025-02-05 14:08:38 +01:00
Günther Noack
e4e1704495 Add deprecation message (#1443) 2025-02-05 13:54:29 +01:00
Tom Burgin
737525b746 kvo static rules (#1425) 2024-09-12 19:23:30 -04:00
Matt W
8199348091 Use runtime platform binary check for exec evals (#1424)
* Use runtime platform binary check for exec evals

* PR Feedback

* Remove parens to mitigate insane clang-formatting
2024-09-10 09:07:50 -04:00
Pete Markowsky
9f41fbb124 Fix: Change uint64 fields in syncv1.proto to uint32 for backwards compatibility (#1422)
Change the uint64 fields in the syncv1.proto to uint32 to ensure backwards compatibility.

This also updates the SNTSyncEventUpload code to use the uint32 values and updates sync protocol docs.
2024-09-08 15:46:30 -04:00
Matt W
ff0efe952b Use proper CanWrite method to safeguard TTY struct access (#1420) 2024-08-21 16:29:33 -04:00
Tom Burgin
c711129ac9 s/NSDictionary/NSBundle/ (#1418) 2024-08-15 12:36:01 -04:00
Russell Hancox
a56f6c5447 Project: Update rules_apple to 3.8.0 (#1417) 2024-08-13 14:20:19 -04:00
Russell Hancox
fadc9b505b sync: Drop rules_* fields in postflight to uint32 (#1415)
* sync: Drop rules_* fields in postflight to uint32

This lets the protobuf json serializer to send the values as ints (like NSJSONSerialization did) instead of strings. This will cause problems if someone has 4B rules but that's probably a sign of bigger problems
2024-08-12 15:40:01 -04:00
Tom Burgin
c7766d5993 length and count check (#1413) 2024-08-09 17:17:13 -04:00
Russell Hancox
341abf044b sync: Fix Content-Type logic bug, add test (#1412) 2024-08-08 17:43:53 -04:00
Tom Burgin
b1cf83a7e3 switch to CFBundleCopyInfoDictionaryInDirectory (#1411) 2024-08-08 16:30:33 -04:00
Russell Hancox
013b0b40af sync: Remove debug logging of request JSON. (#1410)
This was helpful while switching to the protobuf lib but due to the way SLOGD works it isn't limited to just debug invocations and is quite noisy
2024-08-08 14:06:52 -04:00
Russell Hancox
6093118ba1 sync: Improve logging when connection restored, avoid retries. (#1408)
* sync: Improve logging when connection restored, avoid retries.

Don't retry requests if the error is that the machine is offline
2024-07-31 13:29:25 -04:00
Russell Hancox
6719d4c32a sync: Upgrade from SCNetworkReachability -> nw_path_monitor (#1406) 2024-07-31 10:53:16 -04:00
Russell Hancox
1ce4756771 santad: Synchronize access to metric callback array (#1405) 2024-07-29 12:09:03 -04:00
Russell Hancox
9a7dcefb92 sync: Fix serial_num field name (#1404)
Disable the preserve_proto_field_names option when marshalling JSON requests as this prevents the json_name attribute on fields from working properly. Add that attribute to all fields so that they marshal as expected. Stop setting the always_print_enums_as_ints field as the value we're setting to is the default anyway.

Also add a test that preflight request data looks as expected.
2024-07-29 12:08:21 -04:00
Russell Hancox
59382bc3ac sync: Handle missing error string for abnormal statuses (#1402) 2024-07-25 10:48:16 -04:00
Russell Hancox
0725fccc7f Docs: Add DismissText key to configuration.md (#1400) 2024-07-24 15:35:59 -04:00
Russell Hancox
166c0420e5 GUI: Make dismiss button configurable, change default text back to dismiss (#1399) 2024-07-23 13:33:29 -04:00
Matt W
f4ec2d51ab Fix check for deprecated clean sync key (#1397) 2024-07-17 20:10:38 -04:00
Russell Hancox
d54ec98bd5 GUI: Update activation policy for binary blocks (#1396)
Also fix threading issues with the `queueMessage:` method.
2024-07-17 10:29:35 -04:00
Matt W
bbeb653c77 Improve handling of sync response default values (#1395)
* Improve default value handling for sync proto processing

* Fix capitalization of new enum values in comments

* Fix/add tests, update some docs

* Update more docs

* Lint

* Remove comment. Add LEGACY_NAMES tag for the linter
2024-07-16 12:33:31 -04:00
Matt W
52ffe5fc50 sync: Allow empty data for 200 responses (#1394) 2024-07-10 16:50:53 -04:00
Russell Hancox
ffd77fef9d common: Remove debug log when signing ID is missing. (#1393)
This log line gets spit out in santactl fileinfo output in a way that makes the command harder to use for various things
2024-07-10 15:44:07 -04:00
Evangelos Mamalakis
47648d2d5c Handle non-200 HTTP responses in SNTSyncStage performRequest (#1392)
* Handle non-200 HTTP responses in SNTSyncStage performRequest

If we receive a non-200 HTTP response, we should return an error
instead of parsing the response to an empty protobuf message.

* Fix nil check

---------

Co-authored-by: Matt W <436037+mlw@users.noreply.github.com>
2024-07-10 15:43:26 -04:00
Pete Markowsky
208b4a6ebc sync: Add machine_id field to facilitate a GRPC version of the protocol (#1390) 2024-07-09 16:35:26 -04:00
Russell Hancox
7f86366672 sync: Parse response as proto when SyncEnableProtoTransfer enabled (#1391) 2024-07-09 12:24:34 -04:00
Russell Hancox
9e7847740f sync: Handle parse errors, add UNKNOWN_CLIENT_MODE to enum (#1389) 2024-07-09 11:55:06 -04:00
Tom Burgin
348ff8c006 fileinfo: add --filter-inclusive (#1388)
* filter inclusive

* remove print method

* update help
2024-07-09 13:29:37 +00:00
Matt W
476cd21653 Convert santa::santad::event_providers and santa::santad (#1387) 2024-07-05 14:48:22 -04:00
Matt W
7bf11abca0 Convert santa::santad::event_providers::endpoint_security (#1386) 2024-07-05 14:13:10 -04:00
Matt W
466546f548 Namespace simplification pt2 (#1385)
* Convert santa::santad::logs::endpoint_security::serializers::Utilities

* Convert santa::santad::logs::endpoint_security::writers

* Convert santa::santad::logs::endpoint_security::serializers

* Convert santa::santad::logs::endpoint_security and santatest

* Lint

* Change type alias names to not conflict with sysinfo.h
2024-07-05 12:21:01 -04:00
Matt W
73c18851f9 Adopt namespace naming guidelines - part 1 (#1384) 2024-07-04 22:19:32 -04:00
Tom Burgin
650f6fac97 cap q (#1383) 2024-07-03 16:58:47 -04:00
Matt W
9764f1bd69 Use class member access operator for underlying ES message (#1381) 2024-07-03 12:17:00 -04:00
Matt W
688d560b62 Add protobuf serialization for new login/logout events (#1380) 2024-07-03 09:35:25 -04:00
Matt W
b6af5ade60 Add string serialization for new login/logout events (#1379) 2024-07-02 22:40:05 -04:00
Matt W
08ce693096 Login/logout events (#1371)
* WIP Basic new enriched types, hooked up serializers

* WIP Expanded enriched types, finished basic string logging

* WIP Standardize instigator and event user strings.

* WIP Remove sudo event for now. Fix proto types.

* Update proto field names. Fix builds on older SDKs.

* Fix more issues with builds on older SDKs.

* Even more build fixes for older SDKs

* Fix basic string test build on older sdks

* More fixes for older SDKs

* WIP Started on proto encoding and tests

* WIP expanded proto support for new events

* Lint. Fix recorder tests for missing event types

* WIP continued expanding proto support for new events

* WIP finished proto support for all new event types

* WIP Comment all new messages and fields in santa.proto

* WIP Use different impl to set strings to sidestep internal absl issues

* Temporarily removing serializer impls and tests to reduce PR size

* Lint fixes

* PR feedback
2024-07-02 16:41:01 -04:00
Russell Hancox
85cfa641ce Project: Update several bazel modules (#1378) 2024-06-28 13:23:46 -04:00
Matt W
72ed5ee4f9 Drop macos 11 (#1377)
* Drop macOS 11 support

* More changes after rebase, add basic macOS 15 support
2024-06-28 12:58:07 -04:00
Matt W
ecf7040b87 Proto tests min version support (#1376)
* Protobuf event tests should only apply to applicable versions

* Address local/remote lint issues

* Address more local/remote lint issues
2024-06-28 10:52:17 -04:00
Matt W
cedbc0da19 Add tests to ensure EventTypeToString handles all subscriptions (#1374) 2024-06-26 15:46:29 -04:00
Russell Hancox
ef9348e6f5 santad: Fix metrics for AuthSignal events (#1373) 2024-06-26 12:35:30 -04:00
Russell Hancox
b23b528082 docs: Update references to SNTXPCConnection (#1372) 2024-06-21 09:39:12 -04:00
Evangelos Mamalakis
587ac2ddc8 Fix santd title in docs (#1368) 2024-06-18 19:49:16 +02:00
Matt W
14729210d3 Use new Apple docs link for global proxy settings constants (#1367)
* Use new Apple docs link for global proxy settings constants

* Missed a file...

* WIP test workflow change

* WIP Fix link

* Remove trailing whitespace
2024-06-18 13:15:36 -04:00
Toast
c3d29e3c4a docs: Add Identifier Conventions (#1366) 2024-06-18 09:48:03 -04:00
Pete Markowsky
4b0ad39413 Add a Signing ID Format Helper (#1365) 2024-06-11 14:51:23 -04:00
Matt W
e8b7fdff64 Modernize docs (Round 1) (#1363)
* WIP Major modernization effort for many of the Santa docs

* Update IPC concept doc and diagram

* WIP - Apply suggestions from code review

Only some of the comments are included in this first commit.

Co-authored-by: Kathryn May <44557882+kathancox@users.noreply.github.com>
Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>

* WIP - Part 2 - Apply suggestions from code review

Adding some more suggestions. Still more to go through.

Co-authored-by: Kathryn May <44557882+kathancox@users.noreply.github.com>
Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>

* WIP Adding more PR suggestions

* WIP - Apply suggestions from code review

More commits from reviewers

Co-authored-by: Kathryn May <44557882+kathancox@users.noreply.github.com>
Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>

* WIP - Apply suggestions from code review

More PR suggestions

Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>
Co-authored-by: Kathryn May <44557882+kathancox@users.noreply.github.com>

* WIP addressed more PR feedback

* WIP - More PR feedback

* WIP - More PR feedback on bundle identification. Link updates

* WIP - Clarify bundle events

* WIP - clarify how to request bundle binary events

* Update santad setup tasks

* Fix doc link

* Update docs/binaries/santa-gui.md

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

---------

Co-authored-by: Kathryn May <44557882+kathancox@users.noreply.github.com>
Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>
2024-06-07 10:17:07 -04:00
Russell Hancox
35d42d0134 sync: Add option to sync using binary protos (#1364) 2024-06-04 13:53:01 -04:00
Russell Hancox
a42dd6e120 santad: Add signal auth to tamper resistence. (#1360)
Prior to this change, root users could kill the com.google.santa.daemon process. 
It would be immediately restarted by sysextd but this opens a very brief
window where protection is lost. Hooking AUTH_SIGNAL and blocking all
signals to the santad process except those sent by launchd lets us block
this without breaking upgrades, reboots, etc.

This leaves `launchctl kill` and friends as an avenue, so we're also
hooking for exec and blocking executions of launchctl that reference
com.google.santa.daemon except in known safe cases.
2024-06-03 13:41:25 -04:00
Russell Hancox
53a2bbdd1e docs: Document new EventDetailURL keys (#1361) 2024-05-30 12:29:00 -04:00
Pete Markowsky
e417d8847f Project: Update check-markdown workflow to use Lychee (#1362) 2024-05-30 10:10:36 -04:00
Russell Hancox
a23b67d5de sync: Add a protobuf for the existing sync protocol (#1359)
This PR is intended to have no impact on existing sync servers. The fields and enum values in the protobuf have been named such that their JSON equivalents match the existing constants we have in the codebase.

Adding this provides a few benefits:

1. The protobuf serves as canonical documentation of the protocol in a form that's much easier to read than the existing code.
2. Protobuf parsing of JSON is likely to be better than our hand-written version.
3. We can (in a later PR) add a configuration option to use binary encoding instead of JSON, saving network during syncs.
4. Servers written in other languages are easier to write and update as time goes on, especially as we extend the protocol.
2024-05-29 14:22:49 -04:00
Russell Hancox
7502bc247f santactl/fileinfo: Include teamID/platform prefix in signing ID (#1356) 2024-05-21 12:48:48 -04:00
Russell Hancox
cf4dab55e0 santactl/rule: Allow adding signing ID and team ID rules by file path (#1357) 2024-05-21 12:48:27 -04:00
Matt W
e43ad30d4e Fix NSSecureCoding adoption in SNTFileAccessEvent (#1358) 2024-05-21 11:35:07 -04:00
np5
d8928ac320 Add cdhash, teamID, and signingID to bundle events (#1353)
Fix #1352
2024-05-20 10:45:36 -04:00
Matt W
ac1c9d8b05 Fix stat metrics accounting. Refactor setting metrics to be more general. (#1354) 2024-05-17 12:15:48 -04:00
Matt W
9b184ed4fb Add metric for when the file on disk is not the file being evaluated (#1348)
* Add metrics for stat change detection

* Fix test related issues due to partially constructed messages

* lint

* Convert errno to enum class StatResult

* Cleanup from PR feedback
2024-05-16 16:13:29 -04:00
Russell Hancox
67883c5200 GUI: Fix unicode rendering of attributed messages (#1351)
Also added a test to stop this from happening again
2024-05-15 16:27:28 -04:00
Russell Hancox
8e1e155c23 Project: Re-enable layering_checks (#1350) 2024-05-15 14:05:58 -04:00
Russell Hancox
fb6aa850b3 santad: Drop QoS of notify handling queue (#1349)
Bumping from BACKGROUND to DEFAULT had the desired impact of processing events faster and reducing memory usage but had a larger-than-expected increase in CPU usage. UTILITY is in the middle of these two and better fits the desired priority.
2024-05-15 11:53:31 -04:00
czcx
7f06b8c11a Docs: Minor grammar & correctness fixes in known-limitations.md. (#1345) 2024-05-14 13:06:45 -04:00
Matt W
978b33e450 Adopt --preserve-metadata flag to simplify resigning with entitlements (#1346) 2024-05-10 13:40:19 -04:00
Russell Hancox
f00ad32edd santad: Bump QoS of notify handling queue (#1342)
The use of the background queue is a historical artifact from when Santa had its own kernel extension with separate in-kernel queues for processing AUTH & NOTIFY type events. With the move to ES and the larger number of event types that we now notify on, running at the background QoS carries a small risk that the thread processing these events is not given a chance to run often enough that the queue grows and increases memory usage.
2024-05-09 15:44:14 -04:00
Pete Markowsky
7b0d2fdbb8 Add necessary dep for SNTPolicyProcessorTest (#1343) 2024-05-09 15:29:17 -04:00
Russell Hancox
1672e52b7b Project: Disable layering_check in all BUILD files (#1344) 2024-05-09 15:25:19 -04:00
Pete Markowsky
6cca5ab27d Update SNTPolicyProcessor to use a map (#1304)
* Update SNTPolicyProcessor to use a map instead of a giant switch statement

Update SNTPolicyProcessor to use a map instead of a giant switch statement.

Add unit tests for the method that sets SNTCachedDecision values.

* Remove unneccessary OCMock dep in BUILD file.

* Fix typo in method signature.

* Incorporate review feedback.

* Upper case UpdateCachedDecisionSigningInfo

* Update SNTPolicyProcessor.h

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

* Update SNTPolicyProcessor.mm

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

* Fix typo

* Fix linter issues.

* Fixed up more linter issues.

---------

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2024-05-09 14:38:12 -04:00
Matt W
7e4af5e337 Update to Abseil 20240116.1. Fix includes. (#1341) 2024-05-09 12:33:46 -04:00
Russell Hancox
5ea4431901 Project: Move fuzzing rules to bzlmod, fix santa_unit_test (#1339) 2024-05-08 11:50:04 -04:00
Tom Burgin
b53818f556 SNTBlockMessage: add more template options (#1337)
* update event detail url

* refactor template mappings

* re-enable testEventDetailURLForFileAccessEvent

* null

* missed one

* update comment
2024-05-07 09:20:50 -04:00
Tom Burgin
0f5e551345 Project: Fix lint.sh to bubble up all errors, switch from pylint to pyink, fix existing lint errors (#1338) 2024-05-06 16:47:32 -04:00
Nick Gregory
51b0f7146d Testing: update E2E to use JIT runners (#1335)
* inject jit runner token into e2e vm

* split out vm updating

* argparse + logging

* restore update/start vm steps

* pr review comments

* rm gcp.json and verify runner sha
2024-05-06 09:10:04 -04:00
czcx
f5882b3146 docs: Fix grammar and typo in syncing-overview 2024-04-30 12:58:34 -04:00
Rohan Sharma
59c146b4af README: Fix typo in landing page (#1332) 2024-04-30 12:25:53 -04:00
czcx
aaa2b0e259 Docs: Grammar updates on doc index 2024-04-29 17:23:39 -04:00
czcx
9c6fd0677f README: Minor grammar issue fix (#1329) 2024-04-29 15:53:52 -04:00
Russell Hancox
344a35aaf6 Project: Migrate to bazel modules (#1324)
This includes updating to rules_apple 3.5.1 and protobuf 26.1, as well as updating several tests to no longer use the data attribute to pass in testdata.
2024-04-11 17:19:30 -04:00
Matt W
45e36fa501 Bump protobuf to v26.1, update to use new interfaces. (#1317) 2024-04-11 14:22:43 -04:00
Nick Gregory
d5a7c5f1fa ProcessTree: add the first annotation, originator (4/4) (#1296) 2024-04-11 13:35:53 -04:00
Pete Markowsky
22aca6b505 Add macOS-14 to the test matrix. (#1323) 2024-04-05 15:09:04 -04:00
Pete Markowsky
375f7bd9cc Fix: Update code to use the new MOLCodesignChecker interfaces for codesigning info (#1322)
* Update code to use the  new MOLCodesignChecker interfaces for codesigning info.
2024-04-05 12:27:33 -04:00
Matt W
7d58665e87 Bump MOLCodesignChecker tag to latest (#1321) 2024-04-05 10:39:08 -04:00
Ryan Diers
3b2d02f38d GUI: Restore default button type to MessageWindow for blocked events (#1316) 2024-03-28 15:02:01 -04:00
235 changed files with 7374 additions and 4232 deletions

View File

@@ -1 +1 @@
6.3.2
7.0.0

View File

@@ -10,8 +10,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout Santa"
uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # ratchet:actions/checkout@master
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # ratchet:actions/checkout@v4
- name: "Check for deadlinks"
uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # ratchet:gaurav-nelson/github-action-markdown-link-check@v1
uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 # ratchet:lycheeverse/lychee-action@v1
with:
fail: true
- name: "Check for trailing whitespace and newlines"
if: '!cancelled()'
run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"

View File

@@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-11, macos-12, macos-13]
os: [macos-12, macos-13, macos-14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
@@ -31,14 +31,14 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-11, macos-12, macos-13]
os: [macos-12, macos-13, macos-14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
- name: Run All Tests
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=adhoc --test_output=errors
test_coverage:
runs-on: macos-11
runs-on: macos-14
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
- name: Generate test coverage

View File

@@ -6,11 +6,29 @@ on:
workflow_dispatch:
jobs:
update_vm:
runs-on: e2e-host
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
- name: Update VM
env:
GCS_KEY: ${{ secrets.GCS_SERVICE_ACCOUNT_KEY }}
run: |
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp.json
echo "${GCS_KEY}" > ${GOOGLE_APPLICATION_CREDENTIALS}
function cleanup {
rm /tmp/gcp.json
}
trap cleanup EXIT
python3 Testing/integration/actions/update_vm.py macOS_14.bundle.tar.gz
start_vm:
runs-on: e2e-host
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
- name: Start VM
env:
RUNNER_REG_TOKEN: ${{ secrets.RUNNER_REG_TOKEN }}
run: python3 Testing/integration/actions/start_vm.py macOS_14.bundle.tar.gz
integration:

2
.gitignore vendored
View File

@@ -2,7 +2,7 @@
*.profraw
*.provisionprofile
bazel-*
Pods
MODULE.bazel.lock
Santa.xcodeproj/*
Santa.xcworkspace/*
CoverageData/*

5
.pyink-config Normal file
View File

@@ -0,0 +1,5 @@
[tool.pyink]
pyink = true
line-length = 80
pyink-indentation = 2
pyink-use-majority-quotes = true

429
.pylintrc
View File

@@ -1,429 +0,0 @@
# This Pylint rcfile contains a best-effort configuration to uphold the
# best-practices and style described in the Google Python style guide:
# https://google.github.io/styleguide/pyguide.html
#
# Its canonical open-source location is:
# https://google.github.io/styleguide/pylintrc
[MASTER]
# Files or directories to be skipped. They should be base names, not paths.
ignore=third_party
# Files or directories matching the regex patterns are skipped. The regex
# matches against base names, not paths.
ignore-patterns=
# Pickle collected data for later comparisons.
persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
jobs=4
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=abstract-method,
apply-builtin,
arguments-differ,
attribute-defined-outside-init,
backtick,
bad-option-value,
basestring-builtin,
buffer-builtin,
c-extension-no-member,
consider-using-enumerate,
cmp-builtin,
cmp-method,
coerce-builtin,
coerce-method,
delslice-method,
div-method,
duplicate-code,
eq-without-hash,
execfile-builtin,
file-builtin,
filter-builtin-not-iterating,
fixme,
getslice-method,
global-statement,
hex-method,
idiv-method,
implicit-str-concat,
import-error,
import-self,
import-star-module-level,
inconsistent-return-statements,
input-builtin,
intern-builtin,
invalid-str-codec,
locally-disabled,
long-builtin,
long-suffix,
map-builtin-not-iterating,
misplaced-comparison-constant,
missing-function-docstring,
metaclass-assignment,
next-method-called,
next-method-defined,
no-absolute-import,
no-else-break,
no-else-continue,
no-else-raise,
no-else-return,
no-init, # added
no-member,
no-name-in-module,
no-self-use,
nonzero-method,
oct-method,
old-division,
old-ne-operator,
old-octal-literal,
old-raise-syntax,
parameter-unpacking,
print-statement,
raising-string,
range-builtin-not-iterating,
raw_input-builtin,
rdiv-method,
reduce-builtin,
relative-import,
reload-builtin,
round-builtin,
setslice-method,
signature-differs,
standarderror-builtin,
suppressed-message,
sys-max-int,
too-few-public-methods,
too-many-ancestors,
too-many-arguments,
too-many-boolean-expressions,
too-many-branches,
too-many-instance-attributes,
too-many-locals,
too-many-nested-blocks,
too-many-public-methods,
too-many-return-statements,
too-many-statements,
trailing-newlines,
unichr-builtin,
unicode-builtin,
unnecessary-pass,
unpacking-in-except,
useless-else-on-loop,
useless-object-inheritance,
useless-suppression,
using-cmp-argument,
wrong-import-order,
xrange-builtin,
zip-builtin-not-iterating,
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=main,_
# Bad variable names which should always be refused, separated by a comma
bad-names=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
# Regular expression matching correct function names
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
# Regular expression matching correct variable names
variable-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct constant names
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct attribute names
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
# Regular expression matching correct argument names
argument-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class attribute names
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct inline iteration names
inlinevar-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class names
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
# Regular expression matching correct module names
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
# Regular expression matching correct method names
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=10
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
# lines made too long by directives to pytype.
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=(?x)(
^\s*(\#\ )?<?https?://\S+>?$|
^\s*(from\s+\S+\s+)?import\s+.+$)
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=yes
# Maximum number of lines in a module
max-module-lines=99999
# String used as indentation unit. The internal Google style guide mandates 2
# spaces. Google's externaly-published style guide says 4, consistent with
# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
# projects (like TensorFlow).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=TODO
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=yes
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging,absl.logging,tensorflow.io.logging
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,
TERMIOS,
Bastion,
rexec,
sets
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant, absl
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls,
class_
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=StandardError,
Exception,
BaseException

4
BUILD
View File

@@ -1,7 +1,9 @@
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
load("//:helper.bzl", "run_command")
package(default_visibility = ["//:santa_package_group"])
package(
default_visibility = ["//:santa_package_group"],
)
licenses(["notice"])

View File

@@ -53,11 +53,10 @@ readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Inf
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
readonly APP_PKG_SCRIPTS="${SCRATCH}/pkg_scripts"
readonly ENTITLEMENTS="${SCRATCH}/entitlements"
readonly SCRIPT_PATH="$(/usr/bin/dirname -- ${BASH_SOURCE[0]})"
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}"
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
@@ -65,19 +64,9 @@ readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
EN="${ENTITLEMENTS}/${BN}.entitlements"
echo "extracting ${BN} entitlements"
/usr/bin/codesign -d --entitlements "${EN}" "${ARTIFACT}"
if [[ -s "${EN}" ]]; then
EN="--entitlements ${EN}"
else
EN=""
fi
echo "codesigning ${BN}"
/usr/bin/codesign --sign "${SIGNING_IDENTITY}" --keychain "${SIGNING_KEYCHAIN}" \
${EN} --timestamp --force --generate-entitlement-der \
--preserve-metadata=entitlements --timestamp --force --generate-entitlement-der \
--options library,kill,runtime "${ARTIFACT}"
done

56
MODULE.bazel Normal file
View File

@@ -0,0 +1,56 @@
module(name = "santa")
bazel_dep(name = "apple_support", version = "1.15.1", repo_name = "build_bazel_apple_support")
bazel_dep(name = "abseil-cpp", version = "20240116.2", repo_name = "com_google_absl")
bazel_dep(name = "rules_python", version = "0.33.2")
bazel_dep(name = "rules_cc", version = "0.0.9")
bazel_dep(name = "rules_apple", version = "3.8.0", repo_name = "build_bazel_rules_apple")
bazel_dep(name = "rules_swift", version = "2.0.0-rc1", repo_name = "build_bazel_rules_swift")
bazel_dep(name = "rules_fuzzing", version = "0.5.2")
bazel_dep(name = "protobuf", version = "27.2", repo_name = "com_google_protobuf")
bazel_dep(name = "googletest", version = "1.14.0.bcr.1", repo_name = "com_google_googletest")
# MOLCertificate
bazel_dep(name = "molcertificate", version = "2.1", repo_name = "MOLCertificate")
git_override(
module_name = "molcertificate",
commit = "34f0ccf68a34a07cc636ada89057c529f90bec3a",
remote = "https://github.com/google/macops-molcertificate.git",
)
# MOLAuthenticatingURLSession
bazel_dep(name = "molauthenticatingurlsession", version = "3.0", repo_name = "MOLAuthenticatingURLSession")
git_override(
module_name = "molauthenticatingurlsession",
commit = "0a50a67f29d635a4012981714c1dedef9ac25fe6",
remote = "https://github.com/google/macops-molauthenticatingurlsession.git",
)
# MOLCodesignChecker
bazel_dep(name = "molcodesignchecker", version = "3.0", repo_name = "MOLCodesignChecker")
git_override(
module_name = "molcodesignchecker",
commit = "5060bcc8baa90bae3b0ca705d14850328bbbec53",
remote = "https://github.com/google/macops-molcodesignchecker.git",
)
# MOLXPCConnection
bazel_dep(name = "molxpcconnection", version = "2.1", repo_name = "MOLXPCConnection")
git_override(
module_name = "molxpcconnection",
commit = "da816dc49becac96d941ef6a5c4153ed39d1fe7c",
remote = "https://github.com/russellhancox/macops-molxpcconnection.git",
)
# FMDB
non_module_deps = use_extension("//:non_module_deps.bzl", "non_module_deps")
use_repo(non_module_deps, "FMDB")
use_repo(non_module_deps, "OCMock")
# Hedron's Compile Commands Extractor
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
git_override(
module_name = "hedron_compile_commands",
commit = "0e990032f3c5a866e72615cf67e5ce22186dcb97",
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
)

View File

@@ -1,5 +1,12 @@
# Santa
> [!NOTE]
> **As of 2025, Santa is no longer maintained by Google.** We encourage
> existing users to migrate to an actively maintained fork of Santa, such as
> https://github.com/northpolesec/santa.
---
[![license](https://img.shields.io/github/license/google/santa)](https://github.com/google/santa/blob/main/LICENSE)
[![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml)
[![latest release](https://img.shields.io/github/v/release/google/santa.svg)](https://github.com/google/santa/releases/latest)
@@ -21,7 +28,7 @@ It is named Santa because it keeps track of binaries that are naughty or nice.
# Docs
The Santa docs are stored in the
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
[Docs](https://github.com/google/santa/blob/main/docs) directory and are published
at https://santa.dev.
The docs include deployment options, details on how parts of Santa work and

View File

@@ -150,6 +150,16 @@ objc_library(
],
)
objc_library(
name = "SigningIDHelpers",
srcs = ["SigningIDHelpers.m"],
hdrs = ["SigningIDHelpers.h"],
deps = [
":SNTLogging",
"@MOLCodesignChecker",
],
)
objc_library(
name = "SNTBlockMessage",
srcs = ["SNTBlockMessage.m"],
@@ -210,7 +220,7 @@ objc_library(
],
deps = [
":CertificateHelpers",
"@MOLCertificate",
":SNTStoredEvent",
],
)
@@ -477,8 +487,11 @@ santa_unit_test(
santa_unit_test(
name = "SNTBlockMessageTest",
srcs = ["SNTBlockMessageTest.m"],
sdk_frameworks = [
"AppKit",
],
deps = [
":SNTBlockMessage",
":SNTBlockMessage_SantaGUI",
":SNTConfigurator",
":SNTFileAccessEvent",
":SNTStoredEvent",
@@ -524,6 +537,7 @@ objc_library(
"bsm",
],
deps = [
":Platform",
":SystemResources",
"@OCMock",
"@com_google_googletest//:gtest",

View File

@@ -17,13 +17,6 @@
#include <Availability.h>
#if defined(MAC_OS_VERSION_12_0) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
#define HAVE_MACOS_12 1
#else
#define HAVE_MACOS_12 0
#endif
#if defined(MAC_OS_VERSION_13_0) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
#define HAVE_MACOS_13 1
@@ -31,4 +24,18 @@
#define HAVE_MACOS_13 0
#endif
#if defined(MAC_OS_VERSION_14_0) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
#define HAVE_MACOS_14 1
#else
#define HAVE_MACOS_14 0
#endif
#if defined(MAC_OS_VERSION_15_0) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_15_0
#define HAVE_MACOS_15 1
#else
#define HAVE_MACOS_15 0
#endif
#endif

View File

@@ -28,7 +28,7 @@
#define DEBUG_LOG(format, ...) // NOP
#endif
namespace santa::common {
namespace santa {
template <typename ValueT>
class PrefixTree {
@@ -125,7 +125,10 @@ class PrefixTree {
return false;
}
cur_byte = (uint8_t) * ++p;
// Disabling clang format due to local/remote version differences.
// clang-format off
cur_byte = (uint8_t)*++p;
// clang-format on
} while (*p);
node->node_type_ = node_type;
@@ -297,6 +300,6 @@ class PrefixTree {
absl::Mutex lock_;
};
} // namespace santa::common
} // namespace santa
#endif

View File

@@ -17,7 +17,7 @@
#define SANTA_PREFIX_TREE_DEBUG 1
#include "Source/common/PrefixTree.h"
using santa::common::PrefixTree;
using santa::PrefixTree;
@interface PrefixTreeTest : XCTestCase
@end

View File

@@ -20,6 +20,10 @@
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTSystemInfo.h"
static id ValueOrNull(id value) {
return value ?: [NSNull null];
}
@implementation SNTBlockMessage
+ (NSAttributedString *)formatMessage:(NSString *)message {
@@ -52,7 +56,11 @@
#ifdef SANTAGUI
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
NSDictionary *options = @{
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
};
return [[NSAttributedString alloc] initWithHTML:htmlData options:options documentAttributes:NULL];
#else
NSString *strippedHTML = [self stringFromHTML:fullHTML];
if (!strippedHTML) {
@@ -123,35 +131,79 @@
}
+ (NSString *)replaceFormatString:(NSString *)str
withDict:(NSDictionary<NSString *, NSString * (^)()> *)replacements {
withDict:(NSDictionary<NSString *, NSString *> *)replacements {
__block NSString *formatStr = str;
[replacements
enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString * (^computeValue)(), BOOL *stop) {
NSString *value = computeValue();
if (value) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:key withString:value];
}
}];
[replacements enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
if ((id)value != [NSNull null]) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:key withString:value];
}
}];
return formatStr;
}
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
// The following "format strings" will be replaced in the URL, if they are present:
//
// The following "format strings" will be replaced in the URL provided by
// `+eventDetailURLForEvent:customURL:templateMapping:`.
//
// %file_identifier% - The SHA-256 of the binary being executed.
// %bundle_or_file_identifier% - The hash of the bundle containing this file or the file itself,
// if no bundle hash is present.
// %file_bundle_id% - The bundle id of the binary, if any.
// %team_id% - The Team ID if present in the signature information.
// %signing_id% - The Signing ID if present in the signature information.
// %cdhash% - If signed, the CDHash.
// %username% - The executing user's name.
// %machine_id% - The configured machine ID for this host.
// %hostname% - The machine's FQDN.
// %uuid% - The machine's UUID.
// %serial% - The machine's serial number.
//
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
+ (NSDictionary *)eventDetailTemplateMappingForEvent:(SNTStoredEvent *)event {
SNTConfigurator *config = [SNTConfigurator configurator];
return @{
@"%file_sha%" : ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
@"%file_identifier%" : ValueOrNull(event.fileSHA256),
@"%bundle_or_file_identifier%" :
ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
@"%username%" : ValueOrNull(event.executingUser),
@"%file_bundle_id%" : ValueOrNull(event.fileBundleID),
@"%team_id%" : ValueOrNull(event.teamID),
@"%signing_id%" : ValueOrNull(event.signingID),
@"%cdhash%" : ValueOrNull(event.cdhash),
@"%machine_id%" : ValueOrNull(config.machineID),
@"%hostname%" : ValueOrNull([SNTSystemInfo longHostname]),
@"%uuid%" : ValueOrNull([SNTSystemInfo hardwareUUID]),
@"%serial%" : ValueOrNull([SNTSystemInfo serialNumber]),
};
}
//
// Everything from `+eventDetailTemplateMappingForEvent:` with the following file access
// specific templates.
//
// %rule_version% - The version of the rule that was violated.
// %rule_name% - The name of the rule that was violated.
// %accessed_path% - The path accessed by the binary.
//
+ (NSDictionary *)fileAccessEventDetailTemplateMappingForEvent:(SNTFileAccessEvent *)event {
NSMutableDictionary *d = [self eventDetailTemplateMappingForEvent:event].mutableCopy;
[d addEntriesFromDictionary:@{
@"%rule_version%" : ValueOrNull(event.ruleVersion),
@"%rule_name%" : ValueOrNull(event.ruleName),
@"%accessed_path%" : ValueOrNull(event.accessedPath),
}];
return d;
}
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
// The "format strings" in `templateMapping` will be replaced in the URL, if they are present.
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event
customURL:(NSString *)url
templateMapping:(NSDictionary *)templateMapping {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *formatStr = url;
@@ -166,26 +218,7 @@
return nil;
}
// Disabling clang-format. See comment in `eventDetailURLForFileAccessEvent:customURL:`
// clang-format off
NSDictionary<NSString *, NSString * (^)()> *kvReplacements =
[NSDictionary dictionaryWithObjectsAndKeys:
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
^{ return event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil; },
@"%file_sha%",
^{ return event.fileSHA256; }, @"%file_identifier%",
^{ return event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil; },
@"%bundle_or_file_identifier%",
^{ return event.executingUser; }, @"%username%",
^{ return config.machineID; }, @"%machine_id%",
^{ return [SNTSystemInfo longHostname]; }, @"%hostname%",
^{ return [SNTSystemInfo hardwareUUID]; }, @"%uuid%",
^{ return [SNTSystemInfo serialNumber]; }, @"%serial%",
nil];
// clang-format on
formatStr = [SNTBlockMessage replaceFormatString:formatStr withDict:kvReplacements];
formatStr = [SNTBlockMessage replaceFormatString:formatStr withDict:templateMapping];
NSURL *u = [NSURL URLWithString:formatStr];
if (!u) {
LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
@@ -194,55 +227,16 @@
return u;
}
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
// The following "format strings" will be replaced in the URL, if they are present:
//
// %rule_version% - The version of the rule that was violated.
// %rule_name% - The name of the rule that was violated.
// %file_identifier% - The SHA-256 of the binary being executed.
// %accessed_path% - The path accessed by the binary.
// %username% - The executing user's name.
// %machine_id% - The configured machine ID for this host.
// %hostname% - The machine's FQDN.
// %uuid% - The machine's UUID.
// %serial% - The machine's serial number.
//
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
return [self eventDetailURLForEvent:event
customURL:url
templateMapping:[self eventDetailTemplateMappingForEvent:event]];
}
+ (NSURL *)eventDetailURLForFileAccessEvent:(SNTFileAccessEvent *)event customURL:(NSString *)url {
if (!url.length || [url isEqualToString:@"null"]) {
return nil;
}
SNTConfigurator *config = [SNTConfigurator configurator];
// Clang format goes wild here. If you use the container literal syntax `@{}` with a block value
// type, it seems to break the clang format on/off functionality and breaks formatting for the
// remainder of the file.
// Using `dictionaryWithObjectsAndKeys` and disabling clang format as a workaround.
// clang-format off
NSDictionary<NSString *, NSString * (^)()> *kvReplacements =
[NSDictionary dictionaryWithObjectsAndKeys:
^{ return event.ruleVersion; }, @"%rule_version%",
^{ return event.ruleName; }, @"%rule_name%",
^{ return event.fileSHA256; }, @"%file_identifier%",
^{ return event.accessedPath; }, @"%accessed_path%",
^{ return event.executingUser; }, @"%username%",
^{ return config.machineID; }, @"%machine_id%",
^{ return [SNTSystemInfo longHostname]; }, @"%hostname%",
^{ return [SNTSystemInfo hardwareUUID]; }, @"%uuid%",
^{ return [SNTSystemInfo serialNumber]; }, @"%serial%",
nil];
// clang-format on
NSString *formatStr = [SNTBlockMessage replaceFormatString:url withDict:kvReplacements];
NSURL *u = [NSURL URLWithString:formatStr];
if (!u) {
LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
}
return u;
return [self eventDetailURLForEvent:event
customURL:url
templateMapping:[self fileAccessEventDetailTemplateMappingForEvent:event]];
}
@end

View File

@@ -39,18 +39,30 @@
OCMStub([self.mockSystemInfo serialNumber]).andReturn(@"my_s");
}
- (void)testFormatMessage {
NSString *input = @"Testing with somé Ünicode çharacters";
NSAttributedString *got = [SNTBlockMessage formatMessage:input];
XCTAssertEqualObjects([got string], input);
}
- (void)testEventDetailURLForEvent {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.fileSHA256 = @"my_fi";
se.executingUser = @"my_un";
se.fileBundleID = @"s.n.t";
se.cdhash = @"abc";
se.teamID = @"SNT";
se.signingID = @"SNT:s.n.t";
NSString *url = @"http://"
@"localhost?fs=%file_sha%&fi=%file_identifier%&bfi=%bundle_or_file_identifier%&"
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
@"un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
NSString *wantUrl =
@"http://"
@"localhost?fs=my_fi&fi=my_fi&bfi=my_fi&bfi=my_fi&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
NSString *wantUrl = @"http://"
@"localhost?fs=my_fi&fi=my_fi&bfi=my_fi&"
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
@@ -58,7 +70,9 @@
se.fileBundleHash = @"my_fbh";
wantUrl = @"http://"
@"localhost?fs=my_fbh&fi=my_fi&bfi=my_fbh&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
@"localhost?fs=my_fbh&fi=my_fi&bfi=my_fbh&"
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
@@ -74,15 +88,22 @@
fae.ruleVersion = @"my_rv";
fae.ruleName = @"my_rn";
fae.fileSHA256 = @"my_fi";
fae.fileBundleID = @"s.n.t";
fae.cdhash = @"abc";
fae.teamID = @"SNT";
fae.signingID = @"SNT:s.n.t";
fae.accessedPath = @"my_ap";
fae.executingUser = @"my_un";
NSString *url = @"http://"
@"localhost?rv=%rule_version%&rn=%rule_name%&fi=%file_identifier%&ap=%accessed_"
@"path%&un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
NSString *wantUrl =
NSString *url =
@"http://"
@"localhost?rv=my_rv&rn=my_rn&fi=my_fi&ap=my_ap&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
@"localhost?rv=%rule_version%&rn=%rule_name%&fi=%file_identifier%&"
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
@"ap=%accessed_path%&un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
NSString *wantUrl = @"http://"
@"localhost?rv=my_rv&rn=my_rn&fi=my_fi&"
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
@"ap=my_ap&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:url];
@@ -92,4 +113,17 @@
XCTAssertNil([SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:@"null"]);
}
- (void)testEventDetailURLMissingDetails {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.fileSHA256 = @"my_fi";
NSString *url = @"http://localhost?fi=%file_identifier%";
NSString *wantUrl = @"http://localhost?fi=my_fi";
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
XCTAssertEqualObjects(gotUrl.absoluteString, wantUrl);
}
@end

View File

@@ -182,6 +182,7 @@ typedef NS_ENUM(NSInteger, SNTRuleCleanup) {
};
#ifdef __cplusplus
enum class FileAccessPolicyDecision {
kNoPolicy,
kDenied,
@@ -190,6 +191,19 @@ enum class FileAccessPolicyDecision {
kAllowedReadAccess,
kAllowedAuditOnly,
};
enum class StatChangeStep {
kNoChange = 0,
kMessageCreate,
kCodesignValidation,
};
enum class StatResult {
kOK = 0,
kStatError,
kDevnoInodeMismatch,
};
#endif
static const char *kSantaDPath =

View File

@@ -347,6 +347,11 @@
///
@property(readonly, nonatomic) NSString *eventDetailText;
///
/// This string represents the text to show on the "Dismiss" button in the UI instead of "Dismiss".
///
@property(readonly, nonatomic) NSString *dismissText;
///
/// In lockdown mode this is the message shown to the user when an unknown binary
/// is blocked. If this message is not configured, a reasonable default is provided.
@@ -393,11 +398,17 @@
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// If enabled, syncing will use binary protobufs for transfer instead
/// of JSON. Defaults to NO.
///
@property(readonly, nonatomic) BOOL syncEnableProtoTransfer;
///
/// 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.
/// https://developer.apple.com/documentation/cfnetwork/global-proxy-settings-constants.
///
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;

View File

@@ -73,6 +73,7 @@ static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The keys managed by a mobileconfig.
static NSString *const kStaticRules = @"StaticRules";
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kSyncEnableProtoTransfer = @"SyncEnableProtoTransfer";
static NSString *const kSyncProxyConfigKey = @"SyncProxyConfiguration";
static NSString *const kSyncExtraHeadersKey = @"SyncExtraHeaders";
static NSString *const kSyncEnableCleanSyncEventUpload = @"SyncEnableCleanSyncEventUpload";
@@ -96,6 +97,7 @@ static NSString *const kAboutTextKey = @"AboutText";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
static NSString *const kEventDetailTextKey = @"EventDetailText";
static NSString *const kDismissTextKey = @"DismissText";
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
static NSString *const kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
@@ -226,6 +228,7 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
kDismissTextKey : string,
kUnknownBlockMessage : string,
kBannedBlockMessage : string,
kBannedUSBBlockMessage : string,
@@ -234,6 +237,7 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
kModeNotificationLockdown : string,
kStaticRules : array,
kSyncBaseURLKey : string,
kSyncEnableProtoTransfer : number,
kSyncEnableCleanSyncEventUpload : number,
kSyncProxyConfigKey : dictionary,
kSyncExtraHeadersKey : dictionary,
@@ -364,13 +368,22 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
}
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
return [self configStateSet];
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithObject:NSStringFromSelector(@selector(cachedStaticRules))];
});
return set;
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncEnableProtoTransfer {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncExtraHeaders {
return [self configStateSet];
}
@@ -403,6 +416,10 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingDismissText {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
return [self configStateSet];
}
@@ -719,6 +736,11 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
return url;
}
- (BOOL)syncEnableProtoTransfer {
NSNumber *number = self.configState[kSyncEnableProtoTransfer];
return number ? [number boolValue] : NO;
}
- (NSDictionary *)syncProxyConfig {
return self.configState[kSyncProxyConfigKey];
}
@@ -763,6 +785,10 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
return self.configState[kEventDetailTextKey];
}
- (NSString *)dismissText {
return self.configState[kDismissTextKey];
}
- (NSString *)unknownBlockMessage {
return self.configState[kUnknownBlockMessage];
}
@@ -1048,7 +1074,8 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
}
}
if ([action isEqualToString:@"auditonly"]) {
// Note: `auditonly` without an underscore is a deprecated, but still accepted form.
if ([action isEqualToString:@"audit_only"] || [action isEqualToString:@"auditonly"]) {
return SNTOverrideFileAccessActionAuditOnly;
} else if ([action isEqualToString:@"disable"]) {
return SNTOverrideFileAccessActionDiable;

View File

@@ -14,12 +14,12 @@
#import <Foundation/Foundation.h>
#import <MOLCertificate/MOLCertificate.h>
#import "Source/common/SNTStoredEvent.h"
///
/// Represents an event stored in the database.
///
@interface SNTFileAccessEvent : NSObject <NSSecureCoding>
@interface SNTFileAccessEvent : SNTStoredEvent <NSSecureCoding>
///
/// The watched path that was accessed
@@ -32,57 +32,11 @@
@property NSString *ruleVersion;
@property NSString *ruleName;
///
/// The SHA256 of the process that accessed the path
///
@property NSString *fileSHA256;
///
/// The path of the process that accessed the watched path
///
@property NSString *filePath;
///
/// If the process is part of a bundle, the name of the application
///
@property NSString *application;
///
/// If the executed file was signed, this is the Team ID if present in the signature information.
///
@property NSString *teamID;
///
/// If the executed file was signed, this is the Signing ID if present in the signature information.
///
@property NSString *signingID;
///
/// The user who executed the binary.
///
@property NSString *executingUser;
///
/// The process ID of the binary being executed.
///
@property NSNumber *pid;
///
/// The parent process ID of the binary being executed.
///
@property NSNumber *ppid;
///
/// The name of the parent process.
///
@property NSString *parentName;
///
/// If the executed file was signed, this is an NSArray of MOLCertificate's
/// representing the signing chain.
///
@property NSArray<MOLCertificate *> *signingChain;
///
/// A string representing the publisher based on the signingChain
///

View File

@@ -48,35 +48,20 @@
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
ENCODE(accessedPath);
ENCODE(ruleVersion);
ENCODE(ruleName);
ENCODE(fileSHA256);
ENCODE(filePath);
ENCODE(application);
ENCODE(teamID);
ENCODE(teamID);
ENCODE(pid);
ENCODE(ppid);
ENCODE(parentName);
ENCODE(signingChain);
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
self = [super initWithCoder:decoder];
if (self) {
DECODE(accessedPath, NSString);
DECODE(ruleVersion, NSString);
DECODE(ruleName, NSString);
DECODE(fileSHA256, NSString);
DECODE(filePath, NSString);
DECODE(application, NSString);
DECODE(teamID, NSString);
DECODE(teamID, NSString);
DECODE(pid, NSNumber);
DECODE(ppid, NSNumber);
DECODE(parentName, NSString);
DECODEARRAY(signingChain, MOLCertificate);
}
return self;
}

View File

@@ -423,8 +423,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return self.infoDict;
}
d = self.bundle.infoDictionary;
if (d) {
// `-[NSBundle infoDictionary]` is heavily cached, changes to the Info.plist are not realized.
// Use `CFBundleCopyInfoDictionaryInDirectory` instead, which does not appear to cache.
NSString *bundlePath = [self bundlePath];
if (bundlePath.length) {
d = CFBridgingRelease(CFBundleCopyInfoDictionaryInDirectory(
(__bridge CFURLRef)[NSURL fileURLWithPath:bundlePath]));
}
if (d.count) {
self.infoDict = d;
return self.infoDict;
}

View File

@@ -605,10 +605,15 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
/** Export current state of the SNTMetricSet as an NSDictionary. */
- (NSDictionary *)export {
NSDictionary *exported = nil;
NSDictionary *exported;
NSArray *callbacks;
@synchronized(self) {
callbacks = [_callbacks mutableCopy];
}
// Invoke callbacks to ensure metrics are up to date.
for (void (^cb)(void) in _callbacks) {
for (void (^cb)(void) in callbacks) {
cb();
}
@@ -639,20 +644,9 @@ NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType 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;
}
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
formatter.formatOptions =
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
for (NSString *metricName in mutableMetrics[@"metrics"]) {
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];

View File

@@ -54,4 +54,19 @@
///
+ (NSString *)modelIdentifier;
///
/// @return The Santa product version, e.g. 2024.6
///
+ (NSString *)santaProductVersion;
///
/// @return The Santa build version, e.g. 655965194
///
+ (NSString *)santaBuildVersion;
///
/// @return The full Santa versoin, e.g. 2024.6.655965194
///
+ (NSString *)santaFullVersion;
@end

View File

@@ -74,6 +74,21 @@
return @(model);
}
+ (NSString *)santaProductVersion {
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
return info_dict[@"CFBundleShortVersionString"];
}
+ (NSString *)santaBuildVersion {
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
return [[info_dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
}
+ (NSString *)santaFullVersion {
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
return info_dict[@"CFBundleVersion"];
}
#pragma mark - Internal
+ (NSDictionary *)_systemVersionDictionary {

View File

@@ -21,7 +21,7 @@
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
/// Protocol implemented by santabundleservice and utilized by SantaGUI for bundle hashing
@protocol SNTBundleServiceXPC
///

View File

@@ -34,8 +34,7 @@ NSString *const kBundleID = @"com.google.santa.daemon";
#else
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
// "teamid.com.google.santa.daemon.xpc"
NSString *t = cs.signingInformation[@"teamid"];
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
return [NSString stringWithFormat:@"%@.%@.xpc", cs.teamID, kBundleID];
#endif
}

View File

@@ -19,11 +19,11 @@
#include "Source/common/ScopedTypeRef.h"
namespace santa::common {
namespace santa {
template <typename CFT>
using ScopedCFTypeRef = ScopedTypeRef<CFT, (CFT)NULL, CFRetain, CFRelease>;
} // namespace santa::common
} // namespace santa
#endif

View File

@@ -19,7 +19,7 @@
#include "Source/common/ScopedCFTypeRef.h"
using santa::common::ScopedCFTypeRef;
using santa::ScopedCFTypeRef;
@interface ScopedCFTypeRefTest : XCTestCase
@end

View File

@@ -19,7 +19,7 @@
#include "Source/common/ScopedTypeRef.h"
namespace santa::common {
namespace santa {
template <typename IOT>
using ScopedIOObjectRef =
@@ -27,4 +27,4 @@ using ScopedIOObjectRef =
}
#endif
#endif // namespace santa

View File

@@ -20,8 +20,8 @@
#include "Source/common/ScopedIOObjectRef.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h"
using santa::common::ScopedIOObjectRef;
using santa::santad::logs::endpoint_security::serializers::Utilities::GetDefaultIOKitCommsPort;
using santa::GetDefaultIOKitCommsPort;
using santa::ScopedIOObjectRef;
@interface ScopedIOObjectRefTest : XCTestCase
@end

View File

@@ -18,7 +18,7 @@
#include <CoreFoundation/CoreFoundation.h>
#include <assert.h>
namespace santa::common {
namespace santa {
template <typename ElementT, ElementT InvalidV, auto RetainFunc,
auto ReleaseFunc>
@@ -75,6 +75,6 @@ class ScopedTypeRef {
ElementT object_;
};
} // namespace santa::common
} // namespace santa
#endif

View File

@@ -0,0 +1,30 @@
/// Copyright 2024 Google LLC
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
__BEGIN_DECLS
/**
Return a string representing normalized SigningID (prefixed with TeamID and a
colon).
@param csc A MOLCodesignChecker instance
@return An NSString formated as teamID:signingID or nil if there isn't a valid signing ID.
*/
NSString *FormatSigningID(MOLCodesignChecker *csc);
__END_DECLS

View File

@@ -0,0 +1,33 @@
/// Copyright 2024 Google LLC
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SigningIDHelpers.h"
#import "Source/common/SNTLogging.h"
NSString *FormatSigningID(MOLCodesignChecker *csc) {
if (!csc.signingID.length) {
return nil;
}
if (!csc.teamID.length) {
if (csc.platformBinary) {
return [NSString stringWithFormat:@"%@:%@", @"platform", csc.signingID];
} else {
LOGD(@"unable to format signing ID missing team ID for non-platform binary");
return nil;
}
}
return [NSString stringWithFormat:@"%@:%@", csc.teamID, csc.signingID];
}

View File

@@ -15,13 +15,14 @@
#ifndef SANTA__COMMON__STRING_H
#define SANTA__COMMON__STRING_H
#include <EndpointSecurity/ESTypes.h>
#include <Foundation/Foundation.h>
#include <optional>
#include <string>
#include <string_view>
namespace santa::common {
namespace santa {
static inline std::string_view NSStringToUTF8StringView(NSString *str) {
return std::string_view(str.UTF8String, [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
@@ -48,6 +49,10 @@ static inline NSString *OptionalStringToNSString(const std::optional<std::string
}
}
} // namespace santa::common
static inline std::string_view StringTokenToStringView(es_string_token_t es_str) {
return std::string_view(es_str.data, es_str.length);
}
} // namespace santa
#endif

View File

@@ -83,5 +83,6 @@ es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc,
uint64_t future_deadline_ms = 100000);
uint32_t MaxSupportedESMessageVersionForCurrentOS();
uint32_t MinSupportedESMessageVersion(es_event_type_t event_type);
#endif

View File

@@ -19,6 +19,8 @@
#include <mach/mach_time.h>
#include <time.h>
#include <uuid/uuid.h>
#include "Source/common/Platform.h"
#include "Source/common/SystemResources.h"
NSString *RepeatedString(NSString *str, NSUInteger len) {
@@ -64,7 +66,7 @@ struct stat MakeStat(int offset) {
es_string_token_t MakeESStringToken(const char *s) {
return es_string_token_t{
.length = strlen(s),
.length = s ? strlen(s) : 0,
.data = s,
};
}
@@ -91,25 +93,6 @@ es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t par
};
}
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
// Notes:
// 1. ES message v3 was only in betas.
// 2. Message version 7 appeared in macOS 13.3, but features from that are
// not currently used. Leaving off support here so as to not require
// adding v7 test JSON files.
if (@available(macOS 13.0, *)) {
return 6;
} else if (@available(macOS 12.3, *)) {
return 5;
} else if (@available(macOS 11.0, *)) {
return 4;
} else if (@available(macOS 10.15.4, *)) {
return 2;
} else {
return 1;
}
}
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType action_type,
uint64_t future_deadline_ms) {
es_message_t es_msg = {
@@ -134,3 +117,204 @@ void SleepMS(long ms) {
XCTAssertEqual(errno, EINTR);
}
}
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
// Note 1: This function only returns a subset of versions. This is due to the
// minimum supported OS build version as well as features in latest versions
// not currently being used. Capping the max means unnecessary duuplicate test
// JSON files are not needed.
//
// Note 2: The following table maps ES message versions to lmin macOS version:
// ES Version | macOS Version
// 1 | 10.15.0
// 2 | 10.15.4
// 3 | Only in a beta
// 4 | 11.0
// 5 | 12.3
// 6 | 13.0
// 7 | 14.0
// 8 | 15.0
if (@available(macOS 13.0, *)) {
return 6;
} else if (@available(macOS 12.3, *)) {
return 5;
} else {
return 4;
}
}
uint32_t MinSupportedESMessageVersion(es_event_type_t event_type) {
switch (event_type) {
// The following events are available beginning in macOS 10.15
case ES_EVENT_TYPE_AUTH_EXEC:
case ES_EVENT_TYPE_AUTH_OPEN:
case ES_EVENT_TYPE_AUTH_KEXTLOAD:
case ES_EVENT_TYPE_AUTH_MMAP:
case ES_EVENT_TYPE_AUTH_MPROTECT:
case ES_EVENT_TYPE_AUTH_MOUNT:
case ES_EVENT_TYPE_AUTH_RENAME:
case ES_EVENT_TYPE_AUTH_SIGNAL:
case ES_EVENT_TYPE_AUTH_UNLINK:
case ES_EVENT_TYPE_NOTIFY_EXEC:
case ES_EVENT_TYPE_NOTIFY_OPEN:
case ES_EVENT_TYPE_NOTIFY_FORK:
case ES_EVENT_TYPE_NOTIFY_CLOSE:
case ES_EVENT_TYPE_NOTIFY_CREATE:
case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA:
case ES_EVENT_TYPE_NOTIFY_EXIT:
case ES_EVENT_TYPE_NOTIFY_GET_TASK:
case ES_EVENT_TYPE_NOTIFY_KEXTLOAD:
case ES_EVENT_TYPE_NOTIFY_KEXTUNLOAD:
case ES_EVENT_TYPE_NOTIFY_LINK:
case ES_EVENT_TYPE_NOTIFY_MMAP:
case ES_EVENT_TYPE_NOTIFY_MPROTECT:
case ES_EVENT_TYPE_NOTIFY_MOUNT:
case ES_EVENT_TYPE_NOTIFY_UNMOUNT:
case ES_EVENT_TYPE_NOTIFY_IOKIT_OPEN:
case ES_EVENT_TYPE_NOTIFY_RENAME:
case ES_EVENT_TYPE_NOTIFY_SETATTRLIST:
case ES_EVENT_TYPE_NOTIFY_SETEXTATTR:
case ES_EVENT_TYPE_NOTIFY_SETFLAGS:
case ES_EVENT_TYPE_NOTIFY_SETMODE:
case ES_EVENT_TYPE_NOTIFY_SETOWNER:
case ES_EVENT_TYPE_NOTIFY_SIGNAL:
case ES_EVENT_TYPE_NOTIFY_UNLINK:
case ES_EVENT_TYPE_NOTIFY_WRITE:
case ES_EVENT_TYPE_AUTH_FILE_PROVIDER_MATERIALIZE:
case ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_MATERIALIZE:
case ES_EVENT_TYPE_AUTH_FILE_PROVIDER_UPDATE:
case ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_UPDATE:
case ES_EVENT_TYPE_AUTH_READLINK:
case ES_EVENT_TYPE_NOTIFY_READLINK:
case ES_EVENT_TYPE_AUTH_TRUNCATE:
case ES_EVENT_TYPE_NOTIFY_TRUNCATE:
case ES_EVENT_TYPE_AUTH_LINK:
case ES_EVENT_TYPE_NOTIFY_LOOKUP:
case ES_EVENT_TYPE_AUTH_CREATE:
case ES_EVENT_TYPE_AUTH_SETATTRLIST:
case ES_EVENT_TYPE_AUTH_SETEXTATTR:
case ES_EVENT_TYPE_AUTH_SETFLAGS:
case ES_EVENT_TYPE_AUTH_SETMODE:
case ES_EVENT_TYPE_AUTH_SETOWNER: return 1;
// The following events are available beginning in macOS 10.15.1
case ES_EVENT_TYPE_AUTH_CHDIR:
case ES_EVENT_TYPE_NOTIFY_CHDIR:
case ES_EVENT_TYPE_AUTH_GETATTRLIST:
case ES_EVENT_TYPE_NOTIFY_GETATTRLIST:
case ES_EVENT_TYPE_NOTIFY_STAT:
case ES_EVENT_TYPE_NOTIFY_ACCESS:
case ES_EVENT_TYPE_AUTH_CHROOT:
case ES_EVENT_TYPE_NOTIFY_CHROOT:
case ES_EVENT_TYPE_AUTH_UTIMES:
case ES_EVENT_TYPE_NOTIFY_UTIMES:
case ES_EVENT_TYPE_AUTH_CLONE:
case ES_EVENT_TYPE_NOTIFY_CLONE:
case ES_EVENT_TYPE_NOTIFY_FCNTL:
case ES_EVENT_TYPE_AUTH_GETEXTATTR:
case ES_EVENT_TYPE_NOTIFY_GETEXTATTR:
case ES_EVENT_TYPE_AUTH_LISTEXTATTR:
case ES_EVENT_TYPE_NOTIFY_LISTEXTATTR:
case ES_EVENT_TYPE_AUTH_READDIR:
case ES_EVENT_TYPE_NOTIFY_READDIR:
case ES_EVENT_TYPE_AUTH_DELETEEXTATTR:
case ES_EVENT_TYPE_NOTIFY_DELETEEXTATTR:
case ES_EVENT_TYPE_AUTH_FSGETPATH:
case ES_EVENT_TYPE_NOTIFY_FSGETPATH:
case ES_EVENT_TYPE_NOTIFY_DUP:
case ES_EVENT_TYPE_AUTH_SETTIME:
case ES_EVENT_TYPE_NOTIFY_SETTIME:
case ES_EVENT_TYPE_NOTIFY_UIPC_BIND:
case ES_EVENT_TYPE_AUTH_UIPC_BIND:
case ES_EVENT_TYPE_NOTIFY_UIPC_CONNECT:
case ES_EVENT_TYPE_AUTH_UIPC_CONNECT:
case ES_EVENT_TYPE_AUTH_EXCHANGEDATA:
case ES_EVENT_TYPE_AUTH_SETACL:
case ES_EVENT_TYPE_NOTIFY_SETACL: return 1;
// The following events are available beginning in macOS 10.15.4
case ES_EVENT_TYPE_NOTIFY_PTY_GRANT:
case ES_EVENT_TYPE_NOTIFY_PTY_CLOSE:
case ES_EVENT_TYPE_AUTH_PROC_CHECK:
case ES_EVENT_TYPE_NOTIFY_PROC_CHECK:
case ES_EVENT_TYPE_AUTH_GET_TASK: return 2;
// The following events are available beginning in macOS 11.0
case ES_EVENT_TYPE_AUTH_SEARCHFS:
case ES_EVENT_TYPE_NOTIFY_SEARCHFS:
case ES_EVENT_TYPE_AUTH_FCNTL:
case ES_EVENT_TYPE_AUTH_IOKIT_OPEN:
case ES_EVENT_TYPE_AUTH_PROC_SUSPEND_RESUME:
case ES_EVENT_TYPE_NOTIFY_PROC_SUSPEND_RESUME:
case ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED:
case ES_EVENT_TYPE_NOTIFY_GET_TASK_NAME:
case ES_EVENT_TYPE_NOTIFY_TRACE:
case ES_EVENT_TYPE_NOTIFY_REMOTE_THREAD_CREATE:
case ES_EVENT_TYPE_AUTH_REMOUNT:
case ES_EVENT_TYPE_NOTIFY_REMOUNT: return 4;
// The following events are available beginning in macOS 11.3
case ES_EVENT_TYPE_AUTH_GET_TASK_READ:
case ES_EVENT_TYPE_NOTIFY_GET_TASK_READ:
case ES_EVENT_TYPE_NOTIFY_GET_TASK_INSPECT: return 4;
// The following events are available beginning in macOS 12.0
case ES_EVENT_TYPE_NOTIFY_SETUID:
case ES_EVENT_TYPE_NOTIFY_SETGID:
case ES_EVENT_TYPE_NOTIFY_SETEUID:
case ES_EVENT_TYPE_NOTIFY_SETEGID:
case ES_EVENT_TYPE_NOTIFY_SETREUID:
case ES_EVENT_TYPE_NOTIFY_SETREGID:
case ES_EVENT_TYPE_AUTH_COPYFILE:
case ES_EVENT_TYPE_NOTIFY_COPYFILE: return 4;
#if HAVE_MACOS_13
// The following events are available beginning in macOS 13.0
case ES_EVENT_TYPE_NOTIFY_AUTHENTICATION:
case ES_EVENT_TYPE_NOTIFY_XP_MALWARE_DETECTED:
case ES_EVENT_TYPE_NOTIFY_XP_MALWARE_REMEDIATED:
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN:
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT:
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK:
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK:
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH:
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH:
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN:
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT:
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN:
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT:
case ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD:
case ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_REMOVE: return 6;
#endif
#if HAVE_MACOS_14
// The following events are available beginning in macOS 14.0
case ES_EVENT_TYPE_NOTIFY_PROFILE_ADD:
case ES_EVENT_TYPE_NOTIFY_PROFILE_REMOVE:
case ES_EVENT_TYPE_NOTIFY_SU:
case ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_PETITION:
case ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_JUDGEMENT:
case ES_EVENT_TYPE_NOTIFY_SUDO:
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_ADD:
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_REMOVE:
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_SET:
case ES_EVENT_TYPE_NOTIFY_OD_MODIFY_PASSWORD:
case ES_EVENT_TYPE_NOTIFY_OD_DISABLE_USER:
case ES_EVENT_TYPE_NOTIFY_OD_ENABLE_USER:
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_ADD:
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_REMOVE:
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_SET:
case ES_EVENT_TYPE_NOTIFY_OD_CREATE_USER:
case ES_EVENT_TYPE_NOTIFY_OD_CREATE_GROUP:
case ES_EVENT_TYPE_NOTIFY_OD_DELETE_USER:
case ES_EVENT_TYPE_NOTIFY_OD_DELETE_GROUP:
case ES_EVENT_TYPE_NOTIFY_XPC_CONNECT: return 7;
#endif
#if HAVE_MACOS_15
case ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE: return 8;
#endif
default: return UINT32_MAX;
}
}

View File

@@ -15,10 +15,10 @@
#ifndef SANTA__COMMON__UNIT_H
#define SANTA__COMMON__UNIT_H
namespace santa::common {
namespace santa {
struct Unit {};
} // namespace santa::common
} // namespace santa
#endif

View File

@@ -538,6 +538,225 @@ message FileAccess {
optional PolicyDecision policy_decision = 6;
}
// Session identifier for a graphical session
// Note: Identifiers are opaque and have no meaning outside of correlating Santa
// events with the same identifier
message GraphicalSession {
optional uint32 id = 1;
}
// Information about a socket address and its type
message SocketAddress {
// The socket address
optional bytes address = 1;
enum Type {
TYPE_UNKNOWN = 0;
TYPE_NONE = 1;
TYPE_IPV4 = 2;
TYPE_IPV6 = 3;
TYPE_NAMED_SOCKET = 4;
}
// The type of the socket address
optional Type type = 2;
}
// Information about a user logging in via loginwindow
message LoginWindowSessionLogin {
// The process that emitted the login event
optional ProcessInfoLight instigator = 1;
// Name of the user logging in
optional UserInfo user = 2;
// Graphical session information for this session
optional GraphicalSession graphical_session = 3;
}
// Information about a user logging out via loginwindow
message LoginWindowSessionLogout {
// The process that emitted the logout event
optional ProcessInfoLight instigator = 1;
// Name of the user logging out
optional UserInfo user = 2;
// Graphical session information for this session
optional GraphicalSession graphical_session = 3;
}
// Information about a user locking their session via loginwindow
message LoginWindowSessionLock {
// The process that emitted the lock event
optional ProcessInfoLight instigator = 1;
// Name of the user locking their session
optional UserInfo user = 2;
// Graphical session information for this session
optional GraphicalSession graphical_session = 3;
}
// Information about a user unlocking their session via loginwindow
message LoginWindowSessionUnlock {
// The process that emitted the unlock event
optional ProcessInfoLight instigator = 1;
// Name of the user unlocking their session
optional UserInfo user = 2;
// Graphical session information for this session
optional GraphicalSession graphical_session = 3;
}
// Information about loginwindow events
message LoginWindowSession {
oneof event {
LoginWindowSessionLogin login = 1;
LoginWindowSessionLogout logout = 2;
LoginWindowSessionLock lock = 3;
LoginWindowSessionUnlock unlock = 4;
}
}
// Information about a login event from the `login(1)` utility
message Login {
// The process that emitted the login event
optional ProcessInfoLight instigator = 1;
// Whether or not the login was successful
optional bool success = 2;
// Login failure message, if applicable
optional bytes failure_message = 3;
// Information about the user that attempted to log in
// Note: `uid` data may not always exist on failed attempts
optional UserInfo user = 4;
}
// Information about a logout event from the `login(1)` utility
message Logout {
// The process that emitted the logout event
optional ProcessInfoLight instigator = 1;
// Information about the user that logged out
optional UserInfo user = 2;
}
// Information about login and logout events from the `login(1)` utility
message LoginLogout {
oneof event {
Login login = 1;
Logout logout = 2;
}
}
// Information related to Screen Sharing attaching to a graphical session
message ScreenSharingAttach {
// The process that emitted the attach event
optional ProcessInfoLight instigator = 1;
// Whether or not the attach was successful
optional bool success = 2;
// Source address information
optional SocketAddress source = 3;
// Apple ID of the viewer
optional bytes viewer = 4;
// Type of authentication used
optional bytes authentication_type = 5;
// User that attempted authentication, if applicable
optional UserInfo authentication_user = 6;
// Username of the loginwindow session, if available
optional UserInfo session_user = 7;
// Whether or not there was an existing session
optional bool existing_session = 8;
// Graphical session information for this session
optional GraphicalSession graphical_session = 9;
}
// Information related to Screen Sharing detaching from a graphical session
message ScreenSharingDetach {
// The process that emitted the detach event
optional ProcessInfoLight instigator = 1;
// Source address information
optional SocketAddress source = 2;
// Apple ID of the viewer
optional bytes viewer = 3;
// Graphical session information for this session
optional GraphicalSession graphical_session = 4;
}
// Information about Screen Sharing attach and detach events
message ScreenSharing {
oneof event {
ScreenSharingAttach attach = 1;
ScreenSharingDetach detach = 2;
}
}
// Information about SSH login events from the macOS OpenSSH implementation
message OpenSSHLogin {
// The process that emitted the login event
optional ProcessInfoLight instigator = 1;
enum Result {
RESULT_UNKNOWN = 0;
RESULT_LOGIN_EXCEED_MAXTRIES = 1;
RESULT_LOGIN_ROOT_DENIED = 2;
RESULT_AUTH_SUCCESS = 3;
RESULT_AUTH_FAIL_NONE = 4;
RESULT_AUTH_FAIL_PASSWD = 5;
RESULT_AUTH_FAIL_KBDINT = 6;
RESULT_AUTH_FAIL_PUBKEY = 7;
RESULT_AUTH_FAIL_HOSTBASED = 8;
RESULT_AUTH_FAIL_GSSAPI = 9;
RESULT_INVALID_USER = 10;
}
// The result of the login attempt
// Note: Successful if type == `RESULT_AUTH_SUCCESS`
optional Result result = 2;
// Source address of the connection
optional SocketAddress source = 3;
// Name of the user that attempted to login
// Note: `uid` data may not always exist on failed attempts
optional UserInfo user = 4;
}
// Information about SSH logout events from the macOS OpenSSH implementation
message OpenSSHLogout {
// The process that emitted the logout event
optional ProcessInfoLight instigator = 1;
// Source address of the connection
optional SocketAddress source = 2;
// Information about the user that logged out
optional UserInfo user = 3;
}
// Information about login/logout events from the macOS OpenSSH implementation
message OpenSSH {
oneof event {
OpenSSHLogin login = 1;
OpenSSHLogout logout = 2;
}
}
// A message encapsulating a single event
message SantaMessage {
// Machine ID of the host emitting this log
@@ -565,6 +784,10 @@ message SantaMessage {
Allowlist allowlist = 20;
FileAccess file_access = 21;
CodesigningInvalidated codesigning_invalidated = 22;
LoginWindowSession login_window_session = 23;
LoginLogout login_logout = 24;
ScreenSharing screen_sharing = 25;
OpenSSH open_ssh = 26;
};
}

View File

@@ -119,7 +119,7 @@ macos_application(
"//conditions:default": None,
}),
infoplists = ["Info.plist"],
minimum_os_version = "11.0",
minimum_os_version = "12.0",
provisioning_profile = select({
"//:adhoc_build": None,
"//conditions:default": "//profiles:santa_dev",

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -13,23 +13,24 @@
<outlet property="foundFileCountLabel" destination="LHV-gV-vyf" id="Sr0-T2-xGx"/>
<outlet property="hashingIndicator" destination="VyY-Yg-JOe" id="Yq4-tZ-9ep"/>
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
<outlet property="dismissEventButton" destination="BbV-3h-mmL" id="5s4-ZB-xlo"/>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" titled="YES" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<rect key="screenRect" x="0.0" y="0.0" width="1916" height="1099"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="540" height="462"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="16" y="434" width="37" height="32"/>
<rect key="frame" x="16" y="434" width="37" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<buttonCell key="cell" type="roundTextured" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="texturedRounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<userDefinedRuntimeAttributes>
@@ -128,10 +129,6 @@
</textField>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="62" y="235" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
</constraints>
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSInfo" imagePosition="overlaps" alignment="center" refusesFirstResponder="YES" imageScaling="proportionallyDown" inset="2" id="R72-Qy-Xbb">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -139,6 +136,10 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</buttonCell>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
</constraints>
<connections>
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
<binding destination="-2" name="hidden" keyPath="self.publisherInfo" id="fFR-f3-Oiw">
@@ -241,16 +242,17 @@
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="147" y="30" width="126" height="32"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<buttonCell key="cell" type="push" title="Open..." bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<connections>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
@@ -272,30 +274,30 @@ DQ
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="110" y="80" width="319" height="29"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
<connections>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="271" y="28" width="124" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<buttonCell key="cell" type="push" title="Ignore" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<buttonCell key="cell" type="push" title="Dismiss" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<accessibility description="Dismiss Dialog"/>
<connections>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>

View File

@@ -54,6 +54,11 @@
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// Reference to the "Dismiss Event" button in the XIB. Used to update its title.
///
@property(weak) IBOutlet NSButton *dismissEventButton;
///
/// The execution event that this window is for
///

View File

@@ -92,9 +92,18 @@
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
if (eventDetailText) {
[self.openEventButton setTitle:eventDetailText];
// Require the button keyEquivalent set to be CMD + Return
[self.openEventButton setKeyEquivalent:@"\r"]; // Return Key
[self.openEventButton
setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; // Command Key
}
}
NSString *dismissButtonText = [[SNTConfigurator configurator] dismissText];
if (dismissButtonText.length) {
[self.dismissEventButton setTitle:dismissButtonText];
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];

View File

@@ -50,18 +50,9 @@ NS_ASSUME_NONNULL_BEGIN
customMsg:self.attributedCustomMessage];
self.window.delegate = self;
// Add app to Cmd+Tab and Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
[super showWindow:sender];
}
- (void)windowWillClose:(NSNotification *)notification {
// Remove app from Cmd+Tab and Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
[super windowWillClose:notification];
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage formatMessage:self.customMessage];
}

View File

@@ -66,18 +66,9 @@
self.window.delegate = self;
// Make sure app doesn't appear in Cmd+Tab or Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
[super showWindow:sender];
}
- (void)windowWillClose:(NSNotification *)notification {
// Remove app from Cmd+Tab and Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
[super windowWillClose:notification];
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage attributedBlockMessageForFileAccessEvent:self.event
customMessage:self.customMessage];

View File

@@ -142,6 +142,8 @@ struct SNTFileAccessMessageWindowView: View {
Text(customText ?? "Open Event...").frame(maxWidth:.infinity)
})
.buttonStyle(.borderedProminent)
.keyboardShortcut(.defaultAction)
}
Button(action: dismissButton, label: {
Text("Dismiss").frame(maxWidth:.infinity)

View File

@@ -71,6 +71,8 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
[bc resume];
[[bc remoteObjectProxy] spindown];
[bc invalidate];
// Remove app from Cmd+Tab and Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
[NSApp hide:self];
}
}
@@ -101,32 +103,37 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
// If GUI is in silent mode or if there's already a notification queued for
// this message, don't do anything else.
if ([SNTConfigurator configurator].enableSilentMode) return;
if ([self notificationAlreadyQueued:pendingMsg]) return;
// See if this message has been user-silenced.
NSString *messageHash = [pendingMsg messageHash];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:messageHash];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:messageHash];
} else {
LOGI(@"Notification silence: dropping notification for %@", messageHash);
return;
dispatch_async(dispatch_get_main_queue(), ^{
if ([self notificationAlreadyQueued:pendingMsg]) return;
// See if this message has been user-silenced.
NSString *messageHash = [pendingMsg messageHash];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:messageHash];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:messageHash];
} else {
LOGI(@"Notification silence: dropping notification for %@", messageHash);
return;
}
}
}
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
if (!self.currentWindowController) {
[self showQueuedWindow];
}
if (!self.currentWindowController) {
// Add app to Cmd+Tab and Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
[self showQueuedWindow];
}
});
}
// For blocked execution notifications, post an NSDistributedNotificationCenter

View File

@@ -28,24 +28,22 @@
- (OSSystemExtensionReplacementAction)request:(OSSystemExtensionRequest *)request
actionForReplacingExtension:(OSSystemExtensionProperties *)old
withExtension:
(OSSystemExtensionProperties *)new API_AVAILABLE(macos(10.15)) {
withExtension:(OSSystemExtensionProperties *)new {
NSLog(@"SystemExtension \"%@\" request for replacement", request.identifier);
return OSSystemExtensionReplacementActionReplace;
}
- (void)requestNeedsUserApproval:(OSSystemExtensionRequest *)request API_AVAILABLE(macos(10.15)) {
- (void)requestNeedsUserApproval:(OSSystemExtensionRequest *)request {
NSLog(@"SystemExtension \"%@\" request needs user approval", request.identifier);
}
- (void)request:(OSSystemExtensionRequest *)request
didFailWithError:(NSError *)error API_AVAILABLE(macos(10.15)) {
- (void)request:(OSSystemExtensionRequest *)request didFailWithError:(NSError *)error {
NSLog(@"SystemExtension \"%@\" request did fail: %@", request.identifier, error);
exit((int)error.code);
}
- (void)request:(OSSystemExtensionRequest *)request
didFinishWithResult:(OSSystemExtensionRequestResult)result API_AVAILABLE(macos(10.15)) {
didFinishWithResult:(OSSystemExtensionRequestResult)result {
NSLog(@"SystemExtension \"%@\" request did finish: %ld", request.identifier, (long)result);
exit(0);
}

View File

@@ -19,6 +19,7 @@ objc_library(
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SigningIDHelpers",
"@FMDB",
"@MOLCodesignChecker",
"@MOLXPCConnection",
@@ -34,7 +35,7 @@ macos_command_line_application(
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "11.0",
minimum_os_version = "12.0",
provisioning_profile = select({
"//:adhoc_build": None,
"//conditions:default": "//profiles:santa_dev",

View File

@@ -25,6 +25,7 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SigningIDHelpers.h"
@interface SNTBundleService ()
@property MOLXPCConnection *notifierConnection;
@@ -226,6 +227,9 @@
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
se.signingChain = cs.certificates;
se.cdhash = cs.cdhash;
se.teamID = cs.teamID;
se.signingID = FormatSigningID(cs);
dispatch_sync(dispatch_get_main_queue(), ^{
relatedEvents[se.fileSHA256] = se;

View File

@@ -76,6 +76,7 @@ objc_library(
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"//Source/common:SigningIDHelpers",
"//Source/santasyncservice:sync_lib",
"@FMDB",
"@MOLCertificate",
@@ -93,7 +94,7 @@ macos_command_line_application(
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "11.0",
minimum_os_version = "12.0",
provisioning_profile = select({
"//:adhoc_build": None,
"//conditions:default": "//profiles:santa_dev",
@@ -121,6 +122,7 @@ santa_unit_test(
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SigningIDHelpers",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",

View File

@@ -26,6 +26,7 @@
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCBundleServiceInterface.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SigningIDHelpers.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@@ -84,6 +85,7 @@ NSString *formattedStringForKeyArray(NSArray<NSString *> *array) {
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) BOOL bundleInfo;
@property(nonatomic) BOOL filterInclusive;
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic, copy) NSDictionary<NSString *, NSRegularExpression *> *outputFilters;
@@ -188,6 +190,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
@" case-insensitive regular expression which must match anywhere in\n"
@" the keyed property value for the file's info to be displayed.\n"
@" You may specify multiple filters by repeating this flag.\n"
@" If multiple filters are specified, any match will display the\n"
@" file.\n"
@" --filter-inclusive: If multiple filters are specified, they must all match\n"
@" for the file to be displayed.\n"
@" --bundleinfo: If the file is part of a bundle, will also display bundle\n"
@" hash information and hashes of all bundle executables.\n"
@" Incompatible with --recursive and --cert-index.\n"
@@ -380,25 +386,9 @@ REGISTER_COMMAND_NAME(@"fileinfo")
NSError *err;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&err];
NSString *cdhash =
[csc.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoUnique];
NSString *teamID =
[csc.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoTeamIdentifier];
NSString *identifier =
[csc.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoIdentifier];
NSString *signingID;
if (identifier) {
if (teamID) {
signingID = [NSString stringWithFormat:@"%@:%@", teamID, identifier];
} else {
id platformID =
[csc.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoPlatformIdentifier];
if ([platformID isKindOfClass:[NSNumber class]] && [platformID intValue] != 0) {
signingID = [NSString stringWithFormat:@"platform:%@", identifier];
}
}
}
NSString *cdhash = csc.cdhash;
NSString *teamID = csc.teamID;
NSString *signingID = FormatSigningID(csc);
struct RuleIdentifiers identifiers = {
.cdhash = cdhash,
@@ -522,21 +512,22 @@ REGISTER_COMMAND_NAME(@"fileinfo")
- (SNTAttributeBlock)teamID {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
return [csc.signingInformation valueForKey:@"teamid"];
return csc.teamID;
};
}
- (SNTAttributeBlock)signingID {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
return [csc.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoIdentifier];
return FormatSigningID(csc);
};
}
- (SNTAttributeBlock)cdhash {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
return [csc.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoUnique];
return csc.cdhash;
};
}
@@ -614,6 +605,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.qualityOfService = NSQualityOfServiceUserInitiated;
// Limit the number of concurrent operations to 2. By default it is unlimited. Querying for the
// `Rule` results in an XPC message to the santa daemon. On an M1 Max we are
// seeing issues with dropped XPC messages when there are 64 or more in-flight messages. The
// number of in-flight requests to the santa daemon to will be capped to
// `maxConcurrentOperationCount`.
//
// Why 2? We are seeing diminishing wall-time improvements for anything over 2 Qs.
//
// 1 Q
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
// 1.16s user 0.92s system 35% cpu 5.775 total
// 2 Qs
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
// 1.22s user 1.07s system 62% cpu 3.675 total
// 4 Qs
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
// 1.22s user 1.16s system 72% cpu 3.275 total
// 8 Qs
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
// 1.25s user 1.26s system 75% cpu 3.304 total
operationQueue.maxConcurrentOperationCount = 2;
if (isDir && self.recursive) {
NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath:path];
NSString *file = [dirEnum nextObject];
@@ -643,8 +659,29 @@ REGISTER_COMMAND_NAME(@"fileinfo")
[operationQueue waitUntilAllOperationsAreFinished];
}
- (BOOL)shouldOutputValueToDictionary:(NSMutableDictionary *)outputDict
valueForKey:(NSString * (^)(NSString *key))valueForKey {
if (self.outputFilters.count == 0) return YES;
int matches = 0;
for (NSString *key in self.outputFilters) {
NSString *value = valueForKey(key);
NSRegularExpression *regex = self.outputFilters[key];
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
// If this is a value we want to show, store it in the output dictionary.
// This does a linear search on an array, but it's a small array.
if (outputDict && value.length && [self.outputKeyList containsObject:key]) {
outputDict[key] = value;
}
++matches;
}
return self.filterInclusive ? matches == self.outputFilters.count : matches > 0;
}
// Prints out the info for a single (non-directory) file. Which info is printed is controlled
// by the keys in self.outputKeyList.
// TODO: Refactor so this method is testable.
- (void)printInfoForFile:(NSString *)path {
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:path];
if (!fileInfo) {
@@ -678,41 +715,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
NSDictionary *cert = signingChain[index];
// Check if we should skip over this item based on outputFilters.
BOOL filterMatch = self.outputFilters.count == 0;
for (NSString *key in self.outputFilters) {
NSString *value = cert[key] ?: @"";
NSRegularExpression *regex = self.outputFilters[key];
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
filterMatch = YES;
break;
BOOL shouldOutput = [self shouldOutputValueToDictionary:nil
valueForKey:^NSString *(NSString *key) {
return cert[key] ?: @"";
}];
if (!shouldOutput) {
return;
}
if (!filterMatch) return;
// Filter out the info we want now, in case JSON output
for (NSString *key in self.outputKeyList) {
outputDict[key] = cert[key];
}
} else {
// Check if we should skip over this item based on outputFilters. We do this before collecting
// Check if we should skip over this item based on outputFilters. We do this before collecting
// output info because there's a chance that we can bail out early if a filter doesn't match.
// However we also don't want to recompute info, so we save any values that we plan to show.
BOOL filterMatch = self.outputFilters.count == 0;
for (NSString *key in self.outputFilters) {
NSString *value = self.propertyMap[key](self, fileInfo) ?: @"";
NSRegularExpression *regex = self.outputFilters[key];
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
// If this is a value we want to show, store it in the output dictionary.
// This does a linear search on an array, but it's a small array.
if (value.length && [self.outputKeyList containsObject:key]) {
outputDict[key] = value;
}
filterMatch = YES;
break;
BOOL shouldOutput =
[self shouldOutputValueToDictionary:outputDict
valueForKey:^NSString *(NSString *key) {
return self.propertyMap[key](self, fileInfo) ?: @"";
}];
if (!shouldOutput) {
return;
}
if (!filterMatch) return;
// Then fill the outputDict with the rest of the missing values.
for (NSString *key in self.outputKeyList) {
if (outputDict[key]) continue; // ignore keys that we've already set due to a filter
@@ -885,6 +912,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
@"\n--bundleinfo is incompatible with --recursive and --cert-index"];
}
self.bundleInfo = YES;
} else if ([arg caseInsensitiveCompare:@"--filter-inclusive"] == NSOrderedSame) {
self.filterInclusive = YES;
} else {
[paths addObject:arg];
}

View File

@@ -24,6 +24,7 @@
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) BOOL filterInclusive;
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
@@ -250,4 +251,10 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}
- (void)testParseArgumentsFilterInclusiveTrue {
NSArray *filePaths = [self.cfi parseArguments:@[ @"--filter-inclusive", @"/usr/bin/yes" ]];
XCTAssertTrue(self.cfi.filterInclusive);
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
}
@end

View File

@@ -67,7 +67,7 @@ REGISTER_COMMAND_NAME(@"printlog")
- (void)runWithArguments:(NSArray *)arguments {
JsonPrintOptions options;
options.always_print_enums_as_ints = false;
options.always_print_primitive_fields = true;
options.always_print_fields_with_no_presence = true;
options.preserve_proto_field_names = true;
options.add_whitespace = true;

View File

@@ -61,7 +61,8 @@ REGISTER_COMMAND_NAME(@"rule")
@"\n"
@" One of:\n"
@" --path {path}: path of binary/bundle to add/remove.\n"
@" Will add the hash of the file currently at that path.\n"
@" Will add an appropriate rule for the file currently at that path.\n"
@" Defaults to a SHA-256 rule unless overridden with another flag.\n"
@" Does not work with --check. Use the fileinfo verb to check.\n"
@" the rule state of a file.\n"
@" --identifier {sha256|teamID|signingID|cdhash}: identifier to add/remove/check\n"
@@ -259,10 +260,17 @@ REGISTER_COMMAND_NAME(@"rule")
newRule.identifier = cs.leafCertificate.SHA256;
} else if (newRule.type == SNTRuleTypeCDHash) {
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
newRule.identifier =
[cs.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoIdentifier];
} else if (newRule.type == SNTRuleTypeTeamID || newRule.type == SNTRuleTypeSigningID) {
// noop
newRule.identifier = cs.cdhash;
} else if (newRule.type == SNTRuleTypeTeamID) {
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
newRule.identifier = cs.teamID;
} else if (newRule.type == SNTRuleTypeSigningID) {
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
if (cs.teamID.length) {
newRule.identifier = [NSString stringWithFormat:@"%@:%@", cs.teamID, cs.signingID];
} else if (cs.platformBinary) {
newRule.identifier = [NSString stringWithFormat:@"platform:%@", cs.signingID];
}
}
}
@@ -291,7 +299,8 @@ REGISTER_COMMAND_NAME(@"rule")
if (newRule.state == SNTRuleStateUnknown) {
[self printErrorUsageAndExit:@"No state specified"];
} else if (!newRule.identifier) {
[self printErrorUsageAndExit:@"Either SHA-256, team ID, or path to file must be specified"];
[self printErrorUsageAndExit:
@"A valid SHA-256, CDHash, Signing ID, team ID, or path to file must be specified"];
}
[[self.daemonConn remoteObjectProxy]

View File

@@ -193,7 +193,7 @@ objc_library(
objc_library(
name = "SNTPolicyProcessor",
srcs = ["SNTPolicyProcessor.m"],
srcs = ["SNTPolicyProcessor.mm"],
hdrs = ["SNTPolicyProcessor.h"],
deps = [
":SNTRuleTable",
@@ -205,10 +205,24 @@ objc_library(
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTRuleIdentifiers",
"//Source/common:SigningIDHelpers",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@com_google_absl//absl/container:flat_hash_map",
],
)
santa_unit_test(
name = "SNTPolicyProcessorTest",
srcs = ["SNTPolicyProcessorTest.mm"],
deps = [
":SNTPolicyProcessor",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTConfigurator",
"//Source/common:SNTRule",
"//Source/common:TestUtils",
],
)
@@ -321,6 +335,7 @@ objc_library(
":SNTCompilerController",
":SNTEndpointSecurityEventHandler",
":SNTEndpointSecurityTreeAwareClient",
"//Source/common:Platform",
"//Source/common:PrefixTree",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
@@ -458,8 +473,10 @@ objc_library(
hdrs = ["EventProviders/EndpointSecurity/Enricher.h"],
deps = [
":EndpointSecurityEnrichedTypes",
"//Source/common:Platform",
"//Source/common:SNTLogging",
"//Source/common:SantaCache",
"//Source/common:String",
"//Source/santad/ProcessTree:SNTEndpointSecurityAdapter",
"//Source/santad/ProcessTree:process_tree",
],
@@ -538,6 +555,7 @@ objc_library(
":EndpointSecuritySerializer",
":EndpointSecuritySerializerUtilities",
":SNTDecisionCache",
"//Source/common:Platform",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
@@ -553,6 +571,7 @@ objc_library(
":EndpointSecuritySerializer",
":EndpointSecuritySerializerUtilities",
":SNTDecisionCache",
"//Source/common:Platform",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
@@ -645,6 +664,7 @@ objc_library(
deps = [
":EndpointSecurityClient",
":WatchItemPolicy",
"//Source/common:SNTCommonEnums",
"//Source/santad/ProcessTree:process_tree",
],
)
@@ -705,7 +725,9 @@ objc_library(
srcs = ["Metrics.mm"],
hdrs = ["Metrics.h"],
deps = [
":EndpointSecurityMessage",
":SNTApplicationCoreMetrics",
"//Source/common:Platform",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
@@ -779,6 +801,7 @@ objc_library(
"//Source/common:SNTXPCUnprivilegedControlInterface",
"//Source/common:Unit",
"//Source/santad/ProcessTree:process_tree",
"//Source/santad/ProcessTree/annotations:originator",
"@MOLXPCConnection",
],
)
@@ -800,6 +823,7 @@ objc_library(
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SystemResources",
],
@@ -821,7 +845,7 @@ macos_bundle(
}),
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "11.0",
minimum_os_version = "12.0",
provisioning_profile = select({
"//:adhoc_build": None,
"//conditions:default": "//profiles:daemon_dev",
@@ -912,10 +936,6 @@ santa_unit_test(
santa_unit_test(
name = "SantadTest",
srcs = ["SantadTest.mm"],
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
minimum_os_version = "11.0",
sdk_dylibs = [
"bsm",
"EndpointSecurity",
@@ -923,6 +943,9 @@ santa_unit_test(
sdk_frameworks = [
"DiskArbitration",
],
structured_resources = [
"//Source/santad/testdata:binaryrules_testdata",
],
tags = ["exclusive"],
deps = [
":EndpointSecurityMessage",
@@ -948,7 +971,6 @@ santa_unit_test(
srcs = [
"SNTApplicationCoreMetricsTest.mm",
],
minimum_os_version = "11.0",
deps = [
":SNTApplicationCoreMetrics",
"//Source/common:SNTCommonEnums",
@@ -1001,6 +1023,7 @@ santa_unit_test(
":EndpointSecuritySerializerBasicString",
":MockEndpointSecurityAPI",
":SNTDecisionCache",
"//Source/common:Platform",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
@@ -1014,7 +1037,7 @@ santa_unit_test(
santa_unit_test(
name = "EndpointSecuritySerializerProtobufTest",
srcs = ["Logs/EndpointSecurity/Serializers/ProtobufTest.mm"],
data = [
structured_resources = [
"//Source/santad/testdata:protobuf_json_testdata",
],
deps = [
@@ -1025,6 +1048,7 @@ santa_unit_test(
":EndpointSecuritySerializerProtobuf",
":MockEndpointSecurityAPI",
":SNTDecisionCache",
"//Source/common:Platform",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
@@ -1169,7 +1193,10 @@ santa_unit_test(
name = "MetricsTest",
srcs = ["MetricsTest.mm"],
deps = [
":EndpointSecurityMessage",
":Metrics",
":MockEndpointSecurityAPI",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTMetricSet",
"//Source/common:TestUtils",
"@OCMock",
@@ -1334,6 +1361,7 @@ santa_unit_test(
":MockEndpointSecurityAPI",
":SNTCompilerController",
":SNTEndpointSecurityRecorder",
"//Source/common:Platform",
"//Source/common:PrefixTree",
"//Source/common:SNTConfigurator",
"//Source/common:TestUtils",
@@ -1416,10 +1444,13 @@ test_suite(
":SNTEndpointSecurityTamperResistanceTest",
":SNTEventTableTest",
":SNTExecutionControllerTest",
":SNTPolicyProcessorTest",
":SNTRuleTableTest",
":SantadTest",
":WatchItemsTest",
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool_test",
"//Source/santad/ProcessTree:process_tree_test",
"//Source/santad/ProcessTree/annotations:originator_test",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -33,13 +33,7 @@ static const int64_t kTransitiveRuleCullingThreshold = 500000;
// Consider transitive rules out of date if they haven't been used in six months.
static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABLE(macos(12.0)) {
// Note: This function uses API introduced in macOS 12, but we want to continue to support
// building in older environments. API Availability checks do not help for this use case,
// instead we use the following preprocessor macros to conditionally compile these API. The
// drawback here is that if a pre-macOS 12 SDK is used to build Santa and it is then deployed
// on macOS 12 or later, the dynamic mute set will not be computed.
#if HAVE_MACOS_12
static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) {
// Create a temporary ES client in order to grab the default set of muted paths.
// TODO(mlw): Reorganize this code so that a temporary ES client doesn't need to be created
es_client_t *client = NULL;
@@ -69,7 +63,6 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
es_release_muted_paths(mps);
es_delete_client(client);
#endif
}
@interface SNTRuleTable ()
@@ -125,10 +118,8 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
NSMutableSet *superSet = [NSMutableSet setWithSet:fallbackDefaultMuteSet];
[superSet unionSet:santaDefinedCriticalPaths];
if (@available(macOS 12.0, *)) {
// Attempt to add the real default mute set
addPathsFromDefaultMuteSet(superSet);
}
// Attempt to add the real default mute set
addPathsFromDefaultMuteSet(superSet);
criticalPaths = [superSet allObjects];
});

View File

@@ -23,7 +23,7 @@
#include <string_view>
#include <vector>
namespace santa::santad::data_layer {
namespace santa {
enum class WatchItemPathType {
kPrefix,
@@ -117,6 +117,6 @@ struct WatchItemPolicy {
std::string version = "temp_version";
};
} // namespace santa::santad::data_layer
} // namespace santa
#endif

View File

@@ -52,11 +52,11 @@ extern NSString *const kWatchItemConfigKeyProcessesCDHash;
extern NSString *const kWatchItemConfigKeyProcessesPlatformBinary;
// Forward declarations
namespace santa::santad::data_layer {
namespace santa {
class WatchItemsPeer;
}
namespace santa::santad::data_layer {
namespace santa {
struct WatchItemsState {
uint64_t rule_count;
@@ -69,7 +69,7 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
public:
using VersionAndPolicies =
std::pair<std::string, std::vector<std::optional<std::shared_ptr<WatchItemPolicy>>>>;
using WatchItemsTree = santa::common::PrefixTree<std::shared_ptr<WatchItemPolicy>>;
using WatchItemsTree = santa::PrefixTree<std::shared_ptr<WatchItemPolicy>>;
// Factory
static std::shared_ptr<WatchItems> Create(NSString *config_path,
@@ -99,7 +99,7 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
std::pair<NSString *, NSString *> EventDetailLinkInfo(
const std::shared_ptr<WatchItemPolicy> &watch_item);
friend class santa::santad::data_layer::WatchItemsPeer;
friend class santa::WatchItemsPeer;
private:
static std::shared_ptr<WatchItems> CreateInternal(NSString *config_path, NSDictionary *config,
@@ -135,6 +135,6 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
NSString *policy_event_detail_text_ ABSL_GUARDED_BY(lock_);
};
} // namespace santa::santad::data_layer
} // namespace santa
#endif

View File

@@ -38,12 +38,12 @@
#import "Source/common/Unit.h"
#include "Source/santad/DataLayer/WatchItemPolicy.h"
using santa::common::NSStringToUTF8String;
using santa::common::NSStringToUTF8StringView;
using santa::common::PrefixTree;
using santa::common::Unit;
using santa::santad::data_layer::WatchItemPathType;
using santa::santad::data_layer::WatchItemPolicy;
using santa::NSStringToUTF8String;
using santa::NSStringToUTF8StringView;
using santa::PrefixTree;
using santa::Unit;
using santa::WatchItemPathType;
using santa::WatchItemPolicy;
NSString *const kWatchItemConfigKeyVersion = @"Version";
NSString *const kWatchItemConfigKeyEventDetailURL = @"EventDetailURL";
@@ -96,13 +96,15 @@ static constexpr NSUInteger kWatchItemConfigEventDetailTextMaxLength = 48;
// max is used here.
static constexpr NSUInteger kWatchItemConfigEventDetailURLMaxLength = 6000;
namespace santa::santad::data_layer {
namespace santa {
namespace {
// Type aliases
using ValidatorBlock = bool (^)(id, NSError **);
using PathAndTypePair = std::pair<std::string, WatchItemPathType>;
using PathList = std::vector<PathAndTypePair>;
using ProcessList = std::vector<WatchItemPolicy::Process>;
using PathAndTypeVec = std::vector<PathAndTypePair>;
using PolicyProcessVec = std::vector<WatchItemPolicy::Process>;
} // namespace
static void PopulateError(NSError **err, NSString *msg) {
if (err) {
@@ -265,8 +267,8 @@ bool VerifyConfigKeyArray(NSDictionary *dict, NSString *key, Class expected, NSE
/// <true/>
/// </dict>
/// </array>
std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSError **err) {
PathList path_list;
std::variant<Unit, PathAndTypeVec> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSError **err) {
PathAndTypeVec path_list;
for (id path in paths) {
if ([path isKindOfClass:[NSDictionary class]]) {
@@ -336,9 +338,9 @@ std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSEr
/// <string>EEEE</string>
/// </dict>
/// </array>
std::variant<Unit, ProcessList> VerifyConfigWatchItemProcesses(NSDictionary *watch_item,
NSError **err) {
__block ProcessList proc_list;
std::variant<Unit, PolicyProcessVec> VerifyConfigWatchItemProcesses(NSDictionary *watch_item,
NSError **err) {
__block PolicyProcessVec proc_list;
if (!VerifyConfigKeyArray(
watch_item, kWatchItemConfigKeyProcesses, [NSDictionary class], err,
@@ -429,7 +431,7 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
return false;
}
std::variant<Unit, PathList> path_list =
std::variant<Unit, PathAndTypeVec> path_list =
VerifyConfigWatchItemPaths(watch_item[kWatchItemConfigKeyPaths], err);
if (std::holds_alternative<Unit>(path_list)) {
@@ -485,19 +487,19 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
bool enable_silent_tty_mode = GetBoolValue(options, kWatchItemConfigKeyOptionsEnableSilentTTYMode,
kWatchItemPolicyDefaultEnableSilentTTYMode);
std::variant<Unit, ProcessList> proc_list = VerifyConfigWatchItemProcesses(watch_item, err);
std::variant<Unit, PolicyProcessVec> proc_list = VerifyConfigWatchItemProcesses(watch_item, err);
if (std::holds_alternative<Unit>(proc_list)) {
return false;
}
for (const PathAndTypePair &path_type_pair : std::get<PathList>(path_list)) {
for (const PathAndTypePair &path_type_pair : std::get<PathAndTypeVec>(path_list)) {
policies.push_back(std::make_shared<WatchItemPolicy>(
NSStringToUTF8StringView(name), path_type_pair.first, path_type_pair.second,
allow_read_access, audit_only, invert_process_exceptions, enable_silent_mode,
enable_silent_tty_mode,
NSStringToUTF8StringView(options[kWatchItemConfigKeyOptionsCustomMessage]),
options[kWatchItemConfigKeyOptionsEventDetailURL],
options[kWatchItemConfigKeyOptionsEventDetailText], std::get<ProcessList>(proc_list)));
options[kWatchItemConfigKeyOptionsEventDetailText], std::get<PolicyProcessVec>(proc_list)));
}
return true;
@@ -897,4 +899,4 @@ std::pair<NSString *, NSString *> WatchItems::EventDetailLinkInfo(
return {url, text};
}
} // namespace santa::santad::data_layer
} // namespace santa

View File

@@ -32,23 +32,23 @@
#include "Source/santad/DataLayer/WatchItemPolicy.h"
#include "Source/santad/DataLayer/WatchItems.h"
using santa::common::Unit;
using santa::santad::data_layer::kWatchItemPolicyDefaultAllowReadAccess;
using santa::santad::data_layer::kWatchItemPolicyDefaultAuditOnly;
using santa::santad::data_layer::kWatchItemPolicyDefaultInvertProcessExceptions;
using santa::santad::data_layer::kWatchItemPolicyDefaultPathType;
using santa::santad::data_layer::WatchItemPathType;
using santa::santad::data_layer::WatchItemPolicy;
using santa::santad::data_layer::WatchItems;
using santa::santad::data_layer::WatchItemsState;
using santa::kWatchItemPolicyDefaultAllowReadAccess;
using santa::kWatchItemPolicyDefaultAuditOnly;
using santa::kWatchItemPolicyDefaultInvertProcessExceptions;
using santa::kWatchItemPolicyDefaultPathType;
using santa::Unit;
using santa::WatchItemPathType;
using santa::WatchItemPolicy;
using santa::WatchItems;
using santa::WatchItemsState;
namespace santatest {
namespace {
using PathAndTypePair = std::pair<std::string, WatchItemPathType>;
using PathList = std::vector<PathAndTypePair>;
using ProcessList = std::vector<WatchItemPolicy::Process>;
} // namespace santatest
using PathAndTypeVec = std::vector<PathAndTypePair>;
using PolicyProcessVec = std::vector<WatchItemPolicy::Process>;
} // namespace
namespace santa::santad::data_layer {
namespace santa {
extern bool ParseConfig(NSDictionary *config,
std::vector<std::shared_ptr<WatchItemPolicy>> &policies, NSError **err);
@@ -56,10 +56,10 @@ extern bool IsWatchItemNameValid(NSString *watch_item_name, NSError **err);
extern bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
std::vector<std::shared_ptr<WatchItemPolicy>> &policies,
NSError **err);
extern std::variant<Unit, santatest::PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths,
NSError **err);
extern std::variant<Unit, santatest::ProcessList> VerifyConfigWatchItemProcesses(
NSDictionary *watch_item, NSError **err);
extern std::variant<Unit, PathAndTypeVec> VerifyConfigWatchItemPaths(NSArray<id> *paths,
NSError **err);
extern std::variant<Unit, PolicyProcessVec> VerifyConfigWatchItemProcesses(NSDictionary *watch_item,
NSError **err);
class WatchItemsPeer : public WatchItems {
public:
using WatchItems::WatchItems;
@@ -72,14 +72,14 @@ class WatchItemsPeer : public WatchItems {
using WatchItems::embedded_config_;
};
} // namespace santa::santad::data_layer
} // namespace santa
using santa::santad::data_layer::IsWatchItemNameValid;
using santa::santad::data_layer::ParseConfig;
using santa::santad::data_layer::ParseConfigSingleWatchItem;
using santa::santad::data_layer::VerifyConfigWatchItemPaths;
using santa::santad::data_layer::VerifyConfigWatchItemProcesses;
using santa::santad::data_layer::WatchItemsPeer;
using santa::IsWatchItemNameValid;
using santa::ParseConfig;
using santa::ParseConfigSingleWatchItem;
using santa::VerifyConfigWatchItemPaths;
using santa::VerifyConfigWatchItemProcesses;
using santa::WatchItemsPeer;
static constexpr std::string_view kBadPolicyName("__BAD_NAME__");
static constexpr std::string_view kBadPolicyPath("__BAD_PATH__");
@@ -440,7 +440,7 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
}
- (void)testVerifyConfigWatchItemPaths {
std::variant<Unit, santatest::PathList> path_list;
std::variant<Unit, PathAndTypeVec> path_list;
NSError *err;
// Test no paths specified
@@ -466,29 +466,28 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
// Test path array dictionary with default path type
path_list = VerifyConfigWatchItemPaths(@[ @{kWatchItemConfigKeyPathsPath : @"A"} ], &err);
XCTAssertTrue(std::holds_alternative<santatest::PathList>(path_list));
XCTAssertEqual(std::get<santatest::PathList>(path_list).size(), 1);
XCTAssertCStringEqual(std::get<santatest::PathList>(path_list)[0].first.c_str(), "A");
XCTAssertEqual(std::get<santatest::PathList>(path_list)[0].second,
kWatchItemPolicyDefaultPathType);
XCTAssertTrue(std::holds_alternative<PathAndTypeVec>(path_list));
XCTAssertEqual(std::get<PathAndTypeVec>(path_list).size(), 1);
XCTAssertCStringEqual(std::get<PathAndTypeVec>(path_list)[0].first.c_str(), "A");
XCTAssertEqual(std::get<PathAndTypeVec>(path_list)[0].second, kWatchItemPolicyDefaultPathType);
// Test path array dictionary with custom path type
path_list = VerifyConfigWatchItemPaths(
@[ @{kWatchItemConfigKeyPathsPath : @"A", kWatchItemConfigKeyPathsIsPrefix : @(YES)} ], &err);
XCTAssertTrue(std::holds_alternative<santatest::PathList>(path_list));
XCTAssertEqual(std::get<santatest::PathList>(path_list).size(), 1);
XCTAssertCStringEqual(std::get<santatest::PathList>(path_list)[0].first.c_str(), "A");
XCTAssertEqual(std::get<santatest::PathList>(path_list)[0].second, WatchItemPathType::kPrefix);
XCTAssertTrue(std::holds_alternative<PathAndTypeVec>(path_list));
XCTAssertEqual(std::get<PathAndTypeVec>(path_list).size(), 1);
XCTAssertCStringEqual(std::get<PathAndTypeVec>(path_list)[0].first.c_str(), "A");
XCTAssertEqual(std::get<PathAndTypeVec>(path_list)[0].second, WatchItemPathType::kPrefix);
}
- (void)testVerifyConfigWatchItemProcesses {
std::variant<Unit, santatest::ProcessList> proc_list;
std::variant<Unit, PolicyProcessVec> proc_list;
NSError *err;
// Non-existent process list parses successfully, but has no items
proc_list = VerifyConfigWatchItemProcesses(@{}, &err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 0);
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 0);
// Process list fails to parse if contains non-array type
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @""}, &err);
@@ -498,7 +497,7 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @{}}, &err);
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @[]}, &err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
// Test a process dictionary with no valid attributes set
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @[ @{} ]}, &err);
@@ -516,9 +515,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
proc_list = VerifyConfigWatchItemProcesses(
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesBinaryPath : @"mypath"} ]},
&err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
WatchItemPolicy::Process("mypath", "", "", {}, "", std::nullopt));
// Test SigningID length limits
@@ -535,9 +534,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
@[ @{kWatchItemConfigKeyProcessesSigningID : @"com.google.test"} ]
},
&err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
WatchItemPolicy::Process("", "com.google.test", "", {}, "", std::nullopt));
// Test TeamID length limits
@@ -552,9 +551,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
proc_list = VerifyConfigWatchItemProcesses(
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesTeamID : @"myvalidtid"} ]},
&err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
WatchItemPolicy::Process("", "", "myvalidtid", {}, "", std::nullopt));
// Test CDHash length limits
@@ -579,9 +578,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
std::fill(cdhashBytes.begin(), cdhashBytes.end(), 0xAA);
proc_list = VerifyConfigWatchItemProcesses(
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesCDHash : cdhash} ]}, &err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
WatchItemPolicy::Process("", "", "", cdhashBytes, "", std::nullopt));
// Test Cert Hash length limits
@@ -610,9 +609,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesCertificateSha256 : certHash} ]
},
&err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
WatchItemPolicy::Process("", "", "", {}, [certHash UTF8String], std::nullopt));
// Test valid invalid PlatformBinary type
@@ -625,9 +624,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
proc_list = VerifyConfigWatchItemProcesses(
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesPlatformBinary : @(YES)} ]},
&err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
WatchItemPolicy::Process("", "", "", {}, "", std::make_optional(true)));
// Test valid multiple attributes, multiple procs
@@ -652,12 +651,12 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
]
},
&err);
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 2);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 2);
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
WatchItemPolicy::Process("mypath1", "com.google.test1", "validtid_1", cdhashBytes,
[certHash UTF8String], std::make_optional(true)));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[1],
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[1],
WatchItemPolicy::Process("mypath2", "com.google.test2", "validtid_2", cdhashBytes,
[certHash UTF8String], std::make_optional(false)));
}

View File

@@ -27,7 +27,7 @@
#import "Source/common/SantaVnode.h"
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
namespace santa::santad::event_providers {
namespace santa {
enum class FlushCacheMode {
kNonRootOnly,
@@ -52,13 +52,12 @@ class AuthResultCache {
// previously denied binary is allowed, it can be re-executed by the user in a
// timely manner. But the value should be high enough to allow the cache to be
// effective in the event the binary is executed in rapid succession.
static std::unique_ptr<AuthResultCache> Create(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
SNTMetricSet *metric_set, uint64_t cache_deny_time_ms = 1500);
static std::unique_ptr<AuthResultCache> Create(std::shared_ptr<santa::EndpointSecurityAPI> esapi,
SNTMetricSet *metric_set,
uint64_t cache_deny_time_ms = 1500);
AuthResultCache(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
SNTMetricCounter *flush_count, uint64_t cache_deny_time_ms = 1500);
AuthResultCache(std::shared_ptr<santa::EndpointSecurityAPI> esapi, SNTMetricCounter *flush_count,
uint64_t cache_deny_time_ms = 1500);
virtual ~AuthResultCache();
AuthResultCache(AuthResultCache &&other) = delete;
@@ -81,13 +80,13 @@ class AuthResultCache {
SantaCache<SantaVnode, uint64_t> *root_cache_;
SantaCache<SantaVnode, uint64_t> *nonroot_cache_;
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi_;
std::shared_ptr<santa::EndpointSecurityAPI> esapi_;
SNTMetricCounter *flush_count_;
uint64_t root_devno_;
uint64_t cache_deny_time_ns_;
dispatch_queue_t q_;
};
} // namespace santa::santad::event_providers
} // namespace santa
#endif

View File

@@ -22,8 +22,8 @@
#import "Source/common/SantaVnodeHash.h"
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
using santa::santad::event_providers::endpoint_security::Client;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::Client;
using santa::EndpointSecurityAPI;
static NSString *const kFlushCacheReasonClientModeChanged = @"ClientModeChanged";
static NSString *const kFlushCacheReasonPathRegexChanged = @"PathRegexChanged";
@@ -36,7 +36,7 @@ static NSString *const kFlushCacheReasonEntitlementsPrefixFilterChanged =
static NSString *const kFlushCacheReasonEntitlementsTeamIDFilterChanged =
@"EntitlementsTeamIDFilterChanged";
namespace santa::santad::event_providers {
namespace santa {
static inline uint64_t GetCurrentUptime() {
return clock_gettime_nsec_np(CLOCK_MONOTONIC);
@@ -185,4 +185,4 @@ NSArray<NSNumber *> *AuthResultCache::CacheCounts() {
return @[ @(root_cache_->count()), @(nonroot_cache_->count()) ];
}
} // namespace santa::santad::event_providers
} // namespace santa

View File

@@ -27,15 +27,15 @@
#include "Source/santad/EventProviders/AuthResultCache.h"
#include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h"
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
using santa::AuthResultCache;
using santa::FlushCacheMode;
using santa::FlushCacheReason;
namespace santa::santad::event_providers {
namespace santa {
extern NSString *const FlushCacheReasonToString(FlushCacheReason reason);
}
} // namespace santa
using santa::santad::event_providers::FlushCacheReasonToString;
using santa::FlushCacheReasonToString;
// Grab the st_dev number of the root volume to match the root cache
static uint64_t RootDevno() {

View File

@@ -150,15 +150,18 @@ void DARegisterDiskAppearedCallback(DASessionRef session, CFDictionaryRef __null
}];
}
// Disabling clang format due to local/remote version differences.
// clang-format off
void DARegisterDiskDisappearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskDisappearedCallback callback,
void *__nullable context) {};
void *__nullable context) {}
void DARegisterDiskDescriptionChangedCallback(DASessionRef session,
CFDictionaryRef __nullable match,
CFArrayRef __nullable watch,
DADiskDescriptionChangedCallback callback,
void *__nullable context) {};
void *__nullable context) {}
// clang-format on
void DASessionSetDispatchQueue(DASessionRef session, dispatch_queue_t __nullable queue) {
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];

View File

@@ -19,7 +19,7 @@
#include <cstddef>
namespace santa::santad::event_providers::endpoint_security {
namespace santa {
class Client {
public:
@@ -64,6 +64,6 @@ class Client {
es_new_client_result_t result_;
};
} // namespace santa::santad::event_providers::endpoint_security
} // namespace santa
#endif

View File

@@ -20,7 +20,7 @@
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
using santa::santad::event_providers::endpoint_security::Client;
using santa::Client;
// Global semaphore used for custom `es_delete_client` function
dispatch_semaphore_t gSema;

View File

@@ -25,7 +25,7 @@
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
namespace santa::santad::event_providers::endpoint_security {
namespace santa {
class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurityAPI> {
public:
@@ -43,9 +43,9 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
virtual bool InvertTargetPathMuting(const Client &client);
virtual bool MuteTargetPath(const Client &client, std::string_view path,
santa::santad::data_layer::WatchItemPathType path_type);
santa::WatchItemPathType path_type);
virtual bool UnmuteTargetPath(const Client &client, std::string_view path,
santa::santad::data_layer::WatchItemPathType path_type);
santa::WatchItemPathType path_type);
virtual void RetainMessage(const es_message_t *msg);
virtual void ReleaseMessage(const es_message_t *msg);
@@ -69,6 +69,6 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
virtual const es_fd_t *ExecFD(const es_event_exec_t *event, uint32_t index);
};
} // namespace santa::santad::event_providers::endpoint_security
} // namespace santa
#endif

View File

@@ -19,9 +19,9 @@
#include "Source/common/Platform.h"
using santa::santad::data_layer::WatchItemPathType;
using santa::WatchItemPathType;
namespace santa::santad::event_providers::endpoint_security {
namespace santa {
Client EndpointSecurityAPI::NewClient(void (^message_handler)(es_client_t *, Message)) {
es_client_t *client = NULL;
@@ -156,19 +156,11 @@ es_string_token_t EndpointSecurityAPI::ExecEnv(const es_event_exec_t *event, uin
}
uint32_t EndpointSecurityAPI::ExecFDCount(const es_event_exec_t *event) {
if (@available(macOS 11.0, *)) {
return es_exec_fd_count(event);
} else {
return 0;
}
return es_exec_fd_count(event);
}
const es_fd_t *EndpointSecurityAPI::ExecFD(const es_event_exec_t *event, uint32_t index) {
if (@available(macOS 11.0, *)) {
return es_exec_fd(event, index);
} else {
return NULL;
}
return es_exec_fd(event, index);
}
} // namespace santa::santad::event_providers::endpoint_security
} // namespace santa

View File

@@ -28,7 +28,7 @@
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/ProcessTree/process_tree.pb.h"
namespace santa::santad::event_providers::endpoint_security {
namespace santa {
class EnrichedFile {
public:
@@ -151,7 +151,8 @@ class EnrichedEventType {
virtual ~EnrichedEventType() = default;
const es_message_t &es_msg() const { return *es_msg_; }
inline const es_message_t *operator->() const { return es_msg_.operator->(); }
const EnrichedProcess &instigator() const { return instigator_; }
struct timespec enrichment_time() const {
// No reason to return a reference
@@ -340,10 +341,108 @@ class EnrichedCSInvalidated : public EnrichedEventType {
EnrichedCSInvalidated(const EnrichedCSInvalidated &other) = delete;
};
using EnrichedType =
std::variant<EnrichedClose, EnrichedExchange, EnrichedExec, EnrichedExit,
EnrichedFork, EnrichedLink, EnrichedRename, EnrichedUnlink,
EnrichedCSInvalidated>;
// Note: All EnrichedLoginWindowSession* classes currently have the same
// data and implementation. To improve maintainability but still provide
// individual types, an internal EnrichedLoginWindowSession base class is
// defined that is derived by each desired types.
// EnrichedLoginWindowSession is wrapped in an `internal` namespace as it
// shouldn't be directly used outside of this file.
namespace internal {
class EnrichedLoginWindowSession : public EnrichedEventType {
public:
EnrichedLoginWindowSession(Message &&es_msg, EnrichedProcess instigator,
std::optional<uid_t> uid)
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
uid_(std::move(uid)) {}
EnrichedLoginWindowSession(EnrichedLoginWindowSession &&) = default;
virtual ~EnrichedLoginWindowSession() = default;
inline std::optional<uid_t> UID() const { return uid_; }
private:
std::optional<uid_t> uid_;
};
} // namespace internal
class EnrichedLoginWindowSessionLogin
: public internal::EnrichedLoginWindowSession {
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
};
class EnrichedLoginWindowSessionLogout
: public internal::EnrichedLoginWindowSession {
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
};
class EnrichedLoginWindowSessionLock
: public internal::EnrichedLoginWindowSession {
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
};
class EnrichedLoginWindowSessionUnlock
: public internal::EnrichedLoginWindowSession {
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
};
class EnrichedScreenSharingAttach : public EnrichedEventType {
public:
EnrichedScreenSharingAttach(Message &&es_msg, EnrichedProcess instigator)
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
EnrichedScreenSharingAttach(EnrichedScreenSharingAttach &&) = default;
};
class EnrichedScreenSharingDetach : public EnrichedEventType {
public:
EnrichedScreenSharingDetach(Message &&es_msg, EnrichedProcess instigator)
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
EnrichedScreenSharingDetach(EnrichedScreenSharingDetach &&) = default;
};
class EnrichedOpenSSHLogin : public EnrichedEventType {
public:
EnrichedOpenSSHLogin(Message &&es_msg, EnrichedProcess instigator)
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
EnrichedOpenSSHLogin(EnrichedOpenSSHLogin &&) = default;
};
class EnrichedOpenSSHLogout : public EnrichedEventType {
public:
EnrichedOpenSSHLogout(Message &&es_msg, EnrichedProcess instigator)
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
EnrichedOpenSSHLogout(EnrichedOpenSSHLogout &&) = default;
};
class EnrichedLoginLogin : public EnrichedEventType {
public:
EnrichedLoginLogin(Message &&es_msg, EnrichedProcess instigator)
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
EnrichedLoginLogin(EnrichedLoginLogin &&) = default;
};
class EnrichedLoginLogout : public EnrichedEventType {
public:
EnrichedLoginLogout(Message &&es_msg, EnrichedProcess instigator)
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
EnrichedLoginLogout(EnrichedLoginLogout &&) = default;
};
using EnrichedType = std::variant<
EnrichedClose, EnrichedExchange, EnrichedExec, EnrichedExit, EnrichedFork,
EnrichedLink, EnrichedRename, EnrichedUnlink, EnrichedCSInvalidated,
EnrichedLoginWindowSessionLogin, EnrichedLoginWindowSessionLogout,
EnrichedLoginWindowSessionLock, EnrichedLoginWindowSessionUnlock,
EnrichedScreenSharingAttach, EnrichedScreenSharingDetach,
EnrichedOpenSSHLogin, EnrichedOpenSSHLogout, EnrichedLoginLogin,
EnrichedLoginLogout>;
class EnrichedMessage {
public:
@@ -355,6 +454,6 @@ class EnrichedMessage {
EnrichedType msg_;
};
} // namespace santa::santad::event_providers::endpoint_security
} // namespace santa
#endif

View File

@@ -15,12 +15,13 @@
#define SANTA__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_ENRICHER_H
#include <memory>
#include <string_view>
#include "Source/common/SantaCache.h"
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
#include "Source/santad/ProcessTree/process_tree.h"
namespace santa::santad::event_providers::endpoint_security {
namespace santa {
enum class EnrichOptions {
// Specifies default enricher operation.
@@ -33,7 +34,8 @@ enum class EnrichOptions {
class Enricher {
public:
Enricher(std::shared_ptr<process_tree::ProcessTree> pt = nullptr);
Enricher(
std::shared_ptr<santa::santad::process_tree::ProcessTree> pt = nullptr);
virtual ~Enricher() = default;
virtual std::unique_ptr<EnrichedMessage> Enrich(Message &&msg);
virtual EnrichedProcess Enrich(
@@ -47,14 +49,19 @@ class Enricher {
virtual std::optional<std::shared_ptr<std::string>> UsernameForGID(
gid_t gid, EnrichOptions options = EnrichOptions::kDefault);
// This method does not chache. It should not be used on a hot path.
virtual std::optional<uid_t> UIDForUsername(
std::string_view username,
EnrichOptions options = EnrichOptions::kDefault);
private:
SantaCache<uid_t, std::optional<std::shared_ptr<std::string>>>
username_cache_;
SantaCache<gid_t, std::optional<std::shared_ptr<std::string>>>
groupname_cache_;
std::shared_ptr<process_tree::ProcessTree> process_tree_;
std::shared_ptr<santa::santad::process_tree::ProcessTree> process_tree_;
};
} // namespace santa::santad::event_providers::endpoint_security
} // namespace santa
#endif

View File

@@ -23,13 +23,17 @@
#include <memory>
#include <optional>
#include "Source/common/Platform.h"
#include "Source/common/SNTLogging.h"
#include "Source/common/String.h"
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
#include "Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h"
#include "Source/santad/ProcessTree/process_tree.h"
#include "Source/santad/ProcessTree/process_tree_macos.h"
namespace santa::santad::event_providers::endpoint_security {
using santa::StringTokenToStringView;
namespace santa {
Enricher::Enricher(std::shared_ptr<::santa::santad::process_tree::ProcessTree> pt)
: username_cache_(256), groupname_cache_(256), process_tree_(std::move(pt)) {}
@@ -81,6 +85,42 @@ std::unique_ptr<EnrichedMessage> Enricher::Enrich(Message &&es_msg) {
case ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED:
return std::make_unique<EnrichedMessage>(
EnrichedCSInvalidated(std::move(es_msg), Enrich(*es_msg->process)));
#if HAVE_MACOS_13
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN:
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionLogin(
std::move(es_msg), Enrich(*es_msg->process),
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_login->username))));
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT:
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionLogout(
std::move(es_msg), Enrich(*es_msg->process),
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_logout->username))));
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK:
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionLock(
std::move(es_msg), Enrich(*es_msg->process),
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_lock->username))));
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK:
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionUnlock(
std::move(es_msg), Enrich(*es_msg->process),
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_unlock->username))));
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH:
return std::make_unique<EnrichedMessage>(
EnrichedScreenSharingAttach(std::move(es_msg), Enrich(*es_msg->process)));
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH:
return std::make_unique<EnrichedMessage>(
EnrichedScreenSharingDetach(std::move(es_msg), Enrich(*es_msg->process)));
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN:
return std::make_unique<EnrichedMessage>(
EnrichedOpenSSHLogin(std::move(es_msg), Enrich(*es_msg->process)));
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT:
return std::make_unique<EnrichedMessage>(
EnrichedOpenSSHLogout(std::move(es_msg), Enrich(*es_msg->process)));
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN:
return std::make_unique<EnrichedMessage>(
EnrichedLoginLogin(std::move(es_msg), Enrich(*es_msg->process)));
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT:
return std::make_unique<EnrichedMessage>(
EnrichedLoginLogout(std::move(es_msg), Enrich(*es_msg->process)));
#endif
default:
// This is a programming error
LOGE(@"Attempting to enrich an unhandled event type: %d", es_msg->event_type);
@@ -94,9 +134,10 @@ EnrichedProcess Enricher::Enrich(const es_process_t &es_proc, EnrichOptions opti
UsernameForUID(audit_token_to_ruid(es_proc.audit_token), options),
UsernameForGID(audit_token_to_rgid(es_proc.audit_token), options),
Enrich(*es_proc.executable, options),
process_tree_ ? process_tree_->ExportAnnotations(
process_tree::PidFromAuditToken(es_proc.audit_token))
: std::nullopt);
process_tree_
? process_tree_->ExportAnnotations(
santa::santad::process_tree::PidFromAuditToken(es_proc.audit_token))
: std::nullopt);
}
EnrichedFile Enricher::Enrich(const es_file_t &es_file, EnrichOptions options) {
@@ -152,4 +193,14 @@ std::optional<std::shared_ptr<std::string>> Enricher::UsernameForGID(gid_t gid,
}
}
} // namespace santa::santad::event_providers::endpoint_security
std::optional<uid_t> Enricher::UIDForUsername(std::string_view username, EnrichOptions options) {
if (options == EnrichOptions::kLocalOnly) {
// If `kLocalOnly` option is set, do not attempt a lookup
return std::nullopt;
}
struct passwd *pw = getpwnam(username.data());
return pw ? std::make_optional(pw->pw_uid) : std::nullopt;
}
} // namespace santa

View File

@@ -18,7 +18,7 @@
#include "Source/common/TestUtils.h"
#include "Source/santad/EventProviders/EndpointSecurity/Enricher.h"
using santa::santad::event_providers::endpoint_security::Enricher;
using santa::Enricher;
@interface EnricherTest : XCTestCase
@end

View File

@@ -20,11 +20,13 @@
#include <memory>
#include <string>
#import "Source/common/SNTCommonEnums.h"
#include "Source/santad/ProcessTree/process_tree.h"
namespace santa::santad::event_providers::endpoint_security {
namespace santa {
class EndpointSecurityAPI;
class MessagePeer;
class Message {
public:
@@ -39,11 +41,11 @@ class Message {
Message(const Message& other);
Message& operator=(const Message& other) = delete;
void SetProcessToken(process_tree::ProcessToken tok);
void SetProcessToken(santa::santad::process_tree::ProcessToken tok);
// Operators to access underlying es_message_t
const es_message_t* operator->() const { return es_msg_; }
const es_message_t& operator*() const { return *es_msg_; }
inline const es_message_t* operator->() const { return es_msg_; }
inline const es_message_t& operator*() const { return *es_msg_; }
// Helper to get the API associated with this message.
// Used for things like es_exec_arg_count.
@@ -53,14 +55,24 @@ class Message {
std::string ParentProcessName() const;
void UpdateStatState(enum StatChangeStep step) const;
inline StatChangeStep StatChangeStep() const { return stat_change_step_; }
inline StatResult StatResult() const { return stat_result_; }
friend class santa::MessagePeer;
private:
std::shared_ptr<EndpointSecurityAPI> esapi_;
const es_message_t* es_msg_;
std::optional<process_tree::ProcessToken> process_token_;
std::optional<santa::santad::process_tree::ProcessToken> process_token_;
std::string GetProcessName(pid_t pid) const;
mutable enum StatChangeStep stat_change_step_ = StatChangeStep::kNoChange;
mutable enum StatResult stat_result_ = StatResult::kOK;
};
} // namespace santa::santad::event_providers::endpoint_security
} // namespace santa
#endif

View File

@@ -16,14 +16,16 @@
#include <bsm/libbsm.h>
#include <libproc.h>
#include <sys/stat.h>
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
namespace santa::santad::event_providers::endpoint_security {
namespace santa {
Message::Message(std::shared_ptr<EndpointSecurityAPI> esapi, const es_message_t *es_msg)
: esapi_(std::move(esapi)), es_msg_(es_msg), process_token_(std::nullopt) {
esapi_->RetainMessage(es_msg);
UpdateStatState(StatChangeStep::kMessageCreate);
}
Message::~Message() {
@@ -38,6 +40,8 @@ Message::Message(Message &&other) {
other.es_msg_ = nullptr;
process_token_ = std::move(other.process_token_);
other.process_token_ = std::nullopt;
stat_change_step_ = other.stat_change_step_;
stat_result_ = other.stat_result_;
}
Message::Message(const Message &other) {
@@ -45,9 +49,30 @@ Message::Message(const Message &other) {
es_msg_ = other.es_msg_;
esapi_->RetainMessage(es_msg_);
process_token_ = other.process_token_;
stat_change_step_ = other.stat_change_step_;
stat_result_ = other.stat_result_;
}
void Message::SetProcessToken(process_tree::ProcessToken tok) {
void Message::UpdateStatState(enum StatChangeStep step) const {
// Only update state for AUTH EXEC events and if no previous change was detected
if (es_msg_->event_type == ES_EVENT_TYPE_AUTH_EXEC &&
stat_change_step_ == StatChangeStep::kNoChange &&
// Note: The following checks are required due to tests that only
// partially construct an es_message_t.
es_msg_->event.exec.target && es_msg_->event.exec.target->executable) {
struct stat &es_sb = es_msg_->event.exec.target->executable->stat;
struct stat sb;
int ret = stat(es_msg_->event.exec.target->executable->path.data, &sb);
// If stat failed, or if devno/inode changed, update state.
if (ret != 0 || es_sb.st_ino != sb.st_ino || es_sb.st_dev != sb.st_dev) {
stat_change_step_ = step;
// Determine the specific condition that failed for tracking purposes
stat_result_ = (ret != 0) ? StatResult::kStatError : StatResult::kDevnoInodeMismatch;
}
}
}
void Message::SetProcessToken(santa::santad::process_tree::ProcessToken tok) {
process_token_ = std::move(tok);
}
@@ -66,4 +91,4 @@ std::string Message::GetProcessName(pid_t pid) const {
}
}
} // namespace santa::santad::event_providers::endpoint_security
} // namespace santa

View File

@@ -23,7 +23,7 @@
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h"
using santa::santad::event_providers::endpoint_security::Message;
using santa::Message;
bool IsPidInUse(pid_t pid) {
char pname[MAXCOMLEN * 2 + 1] = {};

View File

@@ -27,18 +27,13 @@
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
using santa::santad::event_providers::endpoint_security::Client;
using santa::Client;
class MockEndpointSecurityAPI
: public santa::santad::event_providers::endpoint_security::EndpointSecurityAPI {
class MockEndpointSecurityAPI : public santa::EndpointSecurityAPI {
public:
MOCK_METHOD(santa::santad::event_providers::endpoint_security::Client, NewClient,
(void (^message_handler)(
es_client_t *, santa::santad::event_providers::endpoint_security::Message)));
MOCK_METHOD(santa::Client, NewClient, (void (^message_handler)(es_client_t *, santa::Message)));
MOCK_METHOD(bool, Subscribe,
(const santa::santad::event_providers::endpoint_security::Client &,
const std::set<es_event_type_t> &));
MOCK_METHOD(bool, Subscribe, (const santa::Client &, const std::set<es_event_type_t> &));
MOCK_METHOD(bool, UnsubscribeAll, (const Client &client));
MOCK_METHOD(bool, UnmuteAllPaths, (const Client &client));
@@ -48,30 +43,23 @@ class MockEndpointSecurityAPI
MOCK_METHOD(bool, InvertTargetPathMuting, (const Client &client));
MOCK_METHOD(bool, MuteTargetPath,
(const Client &client, std::string_view path,
santa::santad::data_layer::WatchItemPathType path_type));
(const Client &client, std::string_view path, santa::WatchItemPathType path_type));
MOCK_METHOD(bool, UnmuteTargetPath,
(const Client &client, std::string_view path,
santa::santad::data_layer::WatchItemPathType path_type));
(const Client &client, std::string_view path, santa::WatchItemPathType path_type));
MOCK_METHOD(void, RetainMessage, (const es_message_t *msg));
MOCK_METHOD(void, ReleaseMessage, (const es_message_t *msg));
MOCK_METHOD(bool, RespondAuthResult,
(const santa::santad::event_providers::endpoint_security::Client &,
const santa::santad::event_providers::endpoint_security::Message &msg,
es_auth_result_t result, bool cache));
(const santa::Client &, const santa::Message &msg, es_auth_result_t result,
bool cache));
MOCK_METHOD(bool, RespondFlagsResult,
(const santa::santad::event_providers::endpoint_security::Client &client,
const santa::santad::event_providers::endpoint_security::Message &msg,
uint32_t allowed_flags, bool cache));
(const santa::Client &client, const santa::Message &msg, uint32_t allowed_flags,
bool cache));
MOCK_METHOD(bool, MuteProcess,
(const santa::santad::event_providers::endpoint_security::Client &,
const audit_token_t *tok));
MOCK_METHOD(bool, MuteProcess, (const santa::Client &, const audit_token_t *tok));
MOCK_METHOD(bool, ClearCache,
(const santa::santad::event_providers::endpoint_security::Client &));
MOCK_METHOD(bool, ClearCache, (const santa::Client &));
MOCK_METHOD(uint32_t, ExecArgCount, (const es_event_exec_t *event));
MOCK_METHOD(es_string_token_t, ExecArg, (const es_event_exec_t *event, uint32_t index));
@@ -84,8 +72,7 @@ class MockEndpointSecurityAPI
void SetExpectationsESNewClient() {
EXPECT_CALL(*this, NewClient)
.WillOnce(testing::Return(santa::santad::event_providers::endpoint_security::Client(
nullptr, ES_NEW_CLIENT_RESULT_SUCCESS)));
.WillOnce(testing::Return(santa::Client(nullptr, ES_NEW_CLIENT_RESULT_SUCCESS)));
EXPECT_CALL(*this, MuteProcess).WillOnce(testing::Return(true));
EXPECT_CALL(*this, ClearCache).WillRepeatedly(testing::Return(true));
EXPECT_CALL(*this, Subscribe).WillRepeatedly(testing::Return(true));

View File

@@ -23,11 +23,11 @@
#include "Source/santad/Metrics.h"
// Forward declarations
namespace santa::santad::event_providers {
namespace santa {
class RateLimiterPeer;
}
} // namespace santa
namespace santa::santad::event_providers {
namespace santa {
// Very basic rate limiting infrastructure.
// Currently only handles X events per duration.
@@ -39,12 +39,11 @@ class RateLimiter {
public:
// Factory
static std::shared_ptr<RateLimiter> Create(
std::shared_ptr<santa::santad::Metrics> metrics,
santa::santad::Processor processor, uint16_t max_qps,
NSTimeInterval reset_duration = kDefaultResetDuration);
std::shared_ptr<santa::Metrics> metrics, santa::Processor processor,
uint16_t max_qps, NSTimeInterval reset_duration = kDefaultResetDuration);
RateLimiter(std::shared_ptr<santa::santad::Metrics> metrics,
santa::santad::Processor processor, uint16_t max_qps,
RateLimiter(std::shared_ptr<santa::Metrics> metrics,
santa::Processor processor, uint16_t max_qps,
NSTimeInterval reset_duration);
enum class Decision {
@@ -54,7 +53,7 @@ class RateLimiter {
Decision Decide(uint64_t cur_mach_time);
friend class santa::santad::event_providers::RateLimiterPeer;
friend class santa::RateLimiterPeer;
private:
bool ShouldRateLimitLocked();
@@ -63,8 +62,8 @@ class RateLimiter {
static constexpr NSTimeInterval kDefaultResetDuration = 15.0;
std::shared_ptr<santa::santad::Metrics> metrics_;
santa::santad::Processor processor_;
std::shared_ptr<santa::Metrics> metrics_;
santa::Processor processor_;
size_t log_count_total_ = 0;
size_t max_log_count_total_;
uint64_t reset_mach_time_;
@@ -72,6 +71,6 @@ class RateLimiter {
dispatch_queue_t q_;
};
} // namespace santa::santad::event_providers
} // namespace santa
#endif

View File

@@ -17,10 +17,10 @@
#include "Source/common/BranchPrediction.h"
#include "Source/common/SystemResources.h"
using santa::santad::Metrics;
using santa::santad::Processor;
using santa::Metrics;
using santa::Processor;
namespace santa::santad::event_providers {
namespace santa {
std::shared_ptr<RateLimiter> RateLimiter::Create(std::shared_ptr<Metrics> metrics,
Processor processor, uint16_t max_qps,
@@ -82,4 +82,4 @@ RateLimiter::Decision RateLimiter::Decide(uint64_t cur_mach_time) {
return decision;
}
} // namespace santa::santad::event_providers
} // namespace santa

View File

@@ -20,12 +20,11 @@
#include "Source/common/SystemResources.h"
#include "Source/santad/Metrics.h"
using santa::santad::event_providers::RateLimiter;
using santa::RateLimiter;
static const santa::santad::Processor kDefaultProcessor =
santa::santad::Processor::kFileAccessAuthorizer;
static const santa::Processor kDefaultProcessor = santa::Processor::kFileAccessAuthorizer;
namespace santa::santad::event_providers {
namespace santa {
class RateLimiterPeer : public RateLimiter {
public:
@@ -39,9 +38,9 @@ class RateLimiterPeer : public RateLimiter {
using RateLimiter::reset_mach_time_;
};
} // namespace santa::santad::event_providers
} // namespace santa
using santa::santad::event_providers::RateLimiterPeer;
using santa::RateLimiterPeer;
@interface RateLimiterTest : XCTestCase
@end

View File

@@ -27,14 +27,10 @@
@interface SNTEndpointSecurityAuthorizer
: SNTEndpointSecurityClient <SNTEndpointSecurityEventHandler>
- (instancetype)
initWithESAPI:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)
esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
execController:(SNTExecutionController *)execController
compilerController:(SNTCompilerController *)compilerController
authResultCache:
(std::shared_ptr<santa::santad::event_providers::AuthResultCache>)authResultCache;
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::Metrics>)metrics
execController:(SNTExecutionController *)execController
compilerController:(SNTCompilerController *)compilerController
authResultCache:(std::shared_ptr<santa::AuthResultCache>)authResultCache;
@end

View File

@@ -26,10 +26,10 @@
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/Metrics.h"
using santa::santad::EventDisposition;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::Message;
using santa::AuthResultCache;
using santa::EndpointSecurityAPI;
using santa::EventDisposition;
using santa::Message;
@interface SNTEndpointSecurityAuthorizer ()
@property SNTCompilerController *compilerController;
@@ -41,13 +41,13 @@ using santa::santad::event_providers::endpoint_security::Message;
}
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
metrics:(std::shared_ptr<santa::Metrics>)metrics
execController:(SNTExecutionController *)execController
compilerController:(SNTCompilerController *)compilerController
authResultCache:(std::shared_ptr<AuthResultCache>)authResultCache {
self = [super initWithESAPI:std::move(esApi)
metrics:std::move(metrics)
processor:santa::santad::Processor::kAuthorizer];
processor:santa::Processor::kAuthorizer];
if (self) {
_execController = execController;
_compilerController = compilerController;

View File

@@ -33,9 +33,9 @@
#import "Source/santad/SNTCompilerController.h"
#import "Source/santad/SNTExecutionController.h"
using santa::santad::EventDisposition;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::endpoint_security::Message;
using santa::AuthResultCache;
using santa::EventDisposition;
using santa::Message;
class MockAuthResultCache : public AuthResultCache {
public:
@@ -72,7 +72,7 @@ class MockAuthResultCache : public AuthResultCache {
id authClient =
[[SNTEndpointSecurityAuthorizer alloc] initWithESAPI:mockESApi
metrics:nullptr
processor:santa::santad::Processor::kAuthorizer];
processor:santa::Processor::kAuthorizer];
EXPECT_CALL(*mockESApi, ClearCache)
.After(EXPECT_CALL(*mockESApi, Subscribe(testing::_, expectedEventSubs))
@@ -81,6 +81,10 @@ class MockAuthResultCache : public AuthResultCache {
[authClient enable];
for (const auto &event : expectedEventSubs) {
XCTAssertNoThrow(santa::EventTypeToString(event));
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}

View File

@@ -37,14 +37,14 @@
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/Metrics.h"
using santa::santad::EventDisposition;
using santa::santad::Metrics;
using santa::santad::Processor;
using santa::santad::data_layer::WatchItemPathType;
using santa::santad::event_providers::endpoint_security::Client;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
using santa::santad::event_providers::endpoint_security::Message;
using santa::Client;
using santa::EndpointSecurityAPI;
using santa::EnrichedMessage;
using santa::EventDisposition;
using santa::Message;
using santa::Metrics;
using santa::Processor;
using santa::WatchItemPathType;
constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db",
"/private/var/db/santa/events.db"};
@@ -92,7 +92,7 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
_notifyQueue = dispatch_queue_create(
"com.google.santa.daemon.notify_queue",
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL,
QOS_CLASS_BACKGROUND, 0));
QOS_CLASS_UTILITY, 0));
}
return self;
}
@@ -146,12 +146,10 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
// sequence numbers are processed in order.
self->_metrics->UpdateEventStats(self->_processor, esMsg.operator->());
es_event_type_t eventType = esMsg->event_type;
if ([self handleContextMessage:esMsg]) {
int64_t processingEnd = clock_gettime_nsec_np(CLOCK_MONOTONIC);
self->_metrics->SetEventMetrics(self->_processor, eventType, EventDisposition::kProcessed,
processingEnd - processingStart);
self->_metrics->SetEventMetrics(self->_processor, EventDisposition::kProcessed,
processingEnd - processingStart, esMsg);
return;
}
@@ -159,13 +157,13 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
[self handleMessage:std::move(esMsg)
recordEventMetrics:^(EventDisposition disposition) {
int64_t processingEnd = clock_gettime_nsec_np(CLOCK_MONOTONIC);
self->_metrics->SetEventMetrics(self->_processor, eventType, disposition,
processingEnd - processingStart);
self->_metrics->SetEventMetrics(self->_processor, disposition,
processingEnd - processingStart, esMsg);
}];
} else {
int64_t processingEnd = clock_gettime_nsec_np(CLOCK_MONOTONIC);
self->_metrics->SetEventMetrics(self->_processor, eventType, EventDisposition::kDropped,
processingEnd - processingStart);
self->_metrics->SetEventMetrics(self->_processor, EventDisposition::kDropped,
processingEnd - processingStart, esMsg);
}
});

View File

@@ -29,11 +29,9 @@
@protocol SNTEndpointSecurityClientBase
- (instancetype)
initWithESAPI:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
processor:(santa::santad::Processor)processor;
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::Metrics>)metrics
processor:(santa::Processor)processor;
/// @note If this fails to establish a new ES client via `es_new_client`, an exception is raised
/// that should terminate the program.
@@ -52,9 +50,9 @@
- (bool)unmuteAllTargetPaths;
- (bool)enableTargetPathWatching;
- (bool)muteTargetPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)paths;
- (bool)unmuteTargetPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)paths;
/// Responds to the Message with the given auth result
///
@@ -66,27 +64,18 @@
/// @note If the msg event type requires a flags response, the correct ES API will automatically
/// be called. ALLOWED results will be translated to having all flags set, and DENIED results
/// will be translated to having all flags cleared.
- (bool)respondToMessage:(const santa::santad::event_providers::endpoint_security::Message &)msg
- (bool)respondToMessage:(const santa::Message &)msg
withAuthResult:(es_auth_result_t)result
cacheable:(bool)cacheable;
- (void)
processEnrichedMessage:
(std::unique_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage>)msg
handler:
(void (^)(std::unique_ptr<
santa::santad::event_providers::endpoint_security::EnrichedMessage>))
messageHandler;
- (void)processEnrichedMessage:(std::unique_ptr<santa::EnrichedMessage>)msg
handler:(void (^)(std::unique_ptr<santa::EnrichedMessage>))messageHandler;
- (void)asynchronouslyProcess:(santa::santad::event_providers::endpoint_security::Message)msg
handler:
(void (^)(santa::santad::event_providers::endpoint_security::Message &&))
messageHandler;
- (void)asynchronouslyProcess:(santa::Message)msg
handler:(void (^)(santa::Message &&))messageHandler;
- (void)processMessage:(santa::santad::event_providers::endpoint_security::Message &&)msg
handler:
(void (^)(const santa::santad::event_providers::endpoint_security::Message &))
messageHandler;
- (void)processMessage:(santa::Message &&)msg
handler:(void (^)(const santa::Message &))messageHandler;
- (bool)clearCache;

View File

@@ -34,21 +34,21 @@
#import "Source/santad/EventProviders/SNTEndpointSecurityClient.h"
#include "Source/santad/Metrics.h"
using santa::santad::Processor;
using santa::santad::data_layer::WatchItemPathType;
using santa::santad::event_providers::endpoint_security::Client;
using santa::santad::event_providers::endpoint_security::EnrichedClose;
using santa::santad::event_providers::endpoint_security::EnrichedFile;
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
using santa::santad::event_providers::endpoint_security::EnrichedProcess;
using santa::santad::event_providers::endpoint_security::Message;
using santa::Client;
using santa::EnrichedClose;
using santa::EnrichedFile;
using santa::EnrichedMessage;
using santa::EnrichedProcess;
using santa::Message;
using santa::Processor;
using santa::WatchItemPathType;
@interface SNTEndpointSecurityClient (Testing)
- (void)establishClientOrDie;
- (bool)muteSelf;
- (NSString *)errorMessageForNewClientResult:(es_new_client_result_t)result;
- (void)handleMessage:(Message &&)esMsg
recordEventMetrics:(void (^)(santa::santad::EventDisposition disposition))recordEventMetrics;
recordEventMetrics:(void (^)(santa::EventDisposition disposition))recordEventMetrics;
- (BOOL)shouldHandleMessage:(const Message &)esMsg;
- (int64_t)computeBudgetForDeadline:(uint64_t)deadline currentTime:(uint64_t)currentTime;

View File

@@ -39,17 +39,13 @@ typedef void (^SNTDeviceBlockCallback)(SNTDeviceEvent *event);
@property(nonatomic, readwrite, nullable) NSArray<NSString *> *remountArgs;
@property(nonatomic, nullable) SNTDeviceBlockCallback deviceBlockCallback;
- (instancetype)
initWithESAPI:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)
esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
authResultCache:
(std::shared_ptr<santa::santad::event_providers::AuthResultCache>)authResultCache
blockUSBMount:(BOOL)blockUSBMount
remountUSBMode:(nullable NSArray<NSString *> *)remountUSBMode
startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs;
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::Metrics>)metrics
logger:(std::shared_ptr<santa::Logger>)logger
authResultCache:(std::shared_ptr<santa::AuthResultCache>)authResultCache
blockUSBMount:(BOOL)blockUSBMount
remountUSBMode:(nullable NSArray<NSString *> *)remountUSBMode
startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs;
@end

View File

@@ -33,13 +33,13 @@
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/Metrics.h"
using santa::santad::EventDisposition;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::Message;
using santa::santad::logs::endpoint_security::Logger;
using santa::AuthResultCache;
using santa::EndpointSecurityAPI;
using santa::EventDisposition;
using santa::FlushCacheMode;
using santa::FlushCacheReason;
using santa::Logger;
using santa::Message;
// Defined operations for startup metrics:
// Device shouldn't be operated on (e.g. not a mass storage device)
@@ -178,7 +178,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
metrics:(std::shared_ptr<santa::Metrics>)metrics
logger:(std::shared_ptr<Logger>)logger
authResultCache:(std::shared_ptr<AuthResultCache>)authResultCache
blockUSBMount:(BOOL)blockUSBMount
@@ -186,7 +186,7 @@ NS_ASSUME_NONNULL_BEGIN
startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs {
self = [super initWithESAPI:std::move(esApi)
metrics:std::move(metrics)
processor:santa::santad::Processor::kDeviceManager];
processor:santa::Processor::kDeviceManager];
if (self) {
_logger = logger;
_authResultCache = authResultCache;

View File

@@ -38,11 +38,11 @@
#import "Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.h"
#include "Source/santad/Metrics.h"
using santa::santad::EventDisposition;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
using santa::santad::event_providers::endpoint_security::Message;
using santa::AuthResultCache;
using santa::EventDisposition;
using santa::FlushCacheMode;
using santa::FlushCacheReason;
using santa::Message;
class MockAuthResultCache : public AuthResultCache {
public:
@@ -384,6 +384,8 @@ class MockAuthResultCache : public AuthResultCache {
on:@"v2"
flags:@(MNT_RDONLY | MNT_NOEXEC | MNT_JOURNALED)]];
// Disabling clang format due to local/remote version differences.
// clang-format off
// Create mock disks with desired args
MockDADisk * (^CreateMockDisk)(NSString *, NSString *) =
^MockDADisk *(NSString *mountOn, NSString *mountFrom) {
@@ -396,6 +398,7 @@ class MockAuthResultCache : public AuthResultCache {
return mockDisk;
};
// clang-format on
// Reset the Mock DA property, setup disks and remount args, then trigger the test
void (^PerformStartupTest)(NSArray<MockDADisk *> *, NSArray<NSString *> *,
@@ -495,10 +498,10 @@ class MockAuthResultCache : public AuthResultCache {
};
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
id deviceClient = [[SNTEndpointSecurityDeviceManager alloc]
initWithESAPI:mockESApi
metrics:nullptr
processor:santa::santad::Processor::kDeviceManager];
id deviceClient =
[[SNTEndpointSecurityDeviceManager alloc] initWithESAPI:mockESApi
metrics:nullptr
processor:santa::Processor::kDeviceManager];
EXPECT_CALL(*mockESApi, ClearCache(testing::_))
.After(EXPECT_CALL(*mockESApi, Subscribe(testing::_, expectedEventSubs))
@@ -507,6 +510,10 @@ class MockAuthResultCache : public AuthResultCache {
[deviceClient enable];
for (const auto &event : expectedEventSubs) {
XCTAssertNoThrow(santa::EventTypeToString(event));
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}

View File

@@ -26,8 +26,8 @@
// Called Synchronously and serially for each message provided by the
// EndpointSecurity framework.
- (void)handleMessage:(santa::santad::event_providers::endpoint_security::Message &&)esMsg
recordEventMetrics:(void (^)(santa::santad::EventDisposition))recordEventMetrics;
- (void)handleMessage:(santa::Message &&)esMsg
recordEventMetrics:(void (^)(santa::EventDisposition))recordEventMetrics;
// Called after Santa has finished initializing itself.
// This is an optimal place to subscribe to ES events
@@ -43,13 +43,10 @@
// Called when a client should no longer receive events.
- (void)disable;
- (void)
watchItemsCount:(size_t)count
newPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>>
&)newPaths
removedPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)
removedPaths;
- (void)watchItemsCount:(size_t)count
newPaths:
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)newPaths
removedPaths:
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)removedPaths;
@end

View File

@@ -33,16 +33,13 @@ typedef void (^SNTFileAccessBlockCallback)(SNTFileAccessEvent *event, NSString *
@interface SNTEndpointSecurityFileAccessAuthorizer
: SNTEndpointSecurityClient <SNTEndpointSecurityDynamicEventHandler>
- (instancetype)
initWithESAPI:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
watchItems:(std::shared_ptr<santa::santad::data_layer::WatchItems>)watchItems
enricher:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
decisionCache:(SNTDecisionCache *)decisionCache
ttyWriter:(std::shared_ptr<santa::santad::TTYWriter>)ttyWriter;
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::Metrics>)metrics
logger:(std::shared_ptr<santa::Logger>)logger
watchItems:(std::shared_ptr<santa::WatchItems>)watchItems
enricher:(std::shared_ptr<santa::Enricher>)enricher
decisionCache:(SNTDecisionCache *)decisionCache
ttyWriter:(std::shared_ptr<santa::TTYWriter>)ttyWriter;
@property SNTFileAccessBlockCallback fileAccessBlockCallback;

View File

@@ -51,21 +51,21 @@
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
using santa::common::OptionalStringToNSString;
using santa::common::StringToNSString;
using santa::santad::EventDisposition;
using santa::santad::FileAccessMetricStatus;
using santa::santad::Metrics;
using santa::santad::TTYWriter;
using santa::santad::data_layer::WatchItemPathType;
using santa::santad::data_layer::WatchItemPolicy;
using santa::santad::data_layer::WatchItems;
using santa::santad::event_providers::RateLimiter;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::Enricher;
using santa::santad::event_providers::endpoint_security::EnrichOptions;
using santa::santad::event_providers::endpoint_security::Message;
using santa::santad::logs::endpoint_security::Logger;
using santa::EndpointSecurityAPI;
using santa::Enricher;
using santa::EnrichOptions;
using santa::EventDisposition;
using santa::FileAccessMetricStatus;
using santa::Logger;
using santa::Message;
using santa::Metrics;
using santa::OptionalStringToNSString;
using santa::RateLimiter;
using santa::StringToNSString;
using santa::TTYWriter;
using santa::WatchItemPathType;
using santa::WatchItemPolicy;
using santa::WatchItems;
NSString *kBadCertHash = @"BAD_CERT_HASH";
@@ -396,19 +396,16 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
std::shared_ptr<Metrics> _metrics;
}
- (instancetype)
initWithESAPI:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<Metrics>)metrics
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
watchItems:(std::shared_ptr<WatchItems>)watchItems
enricher:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
decisionCache:(SNTDecisionCache *)decisionCache
ttyWriter:(std::shared_ptr<santa::santad::TTYWriter>)ttyWriter {
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<Metrics>)metrics
logger:(std::shared_ptr<santa::Logger>)logger
watchItems:(std::shared_ptr<WatchItems>)watchItems
enricher:(std::shared_ptr<santa::Enricher>)enricher
decisionCache:(SNTDecisionCache *)decisionCache
ttyWriter:(std::shared_ptr<santa::TTYWriter>)ttyWriter {
self = [super initWithESAPI:std::move(esApi)
metrics:metrics
processor:santa::santad::Processor::kFileAccessAuthorizer];
processor:santa::Processor::kFileAccessAuthorizer];
if (self) {
_watchItems = std::move(watchItems);
_logger = std::move(logger);
@@ -419,8 +416,8 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
_configurator = [SNTConfigurator configurator];
_rateLimiter = RateLimiter::Create(_metrics, santa::santad::Processor::kFileAccessAuthorizer,
kDefaultRateLimitQPS);
_rateLimiter =
RateLimiter::Create(_metrics, santa::Processor::kFileAccessAuthorizer, kDefaultRateLimitQPS);
SNTMetricBooleanGauge *famEnabled = [[SNTMetricSet sharedInstance]
booleanGaugeWithName:@"/santa/fam_enabled"
@@ -691,7 +688,7 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
// Notify users on block decisions
if (ShouldNotifyUserDecision(policyDecision) &&
(!policy->silent || (!policy->silent_tty && msg->process->tty->path.length > 0))) {
(!policy->silent || (!policy->silent_tty && TTYWriter::CanWrite(msg->process)))) {
SNTCachedDecision *cd =
[self.decisionCache cachedDecisionForFile:msg->process->executable->stat];
@@ -797,7 +794,7 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
cacheable:(policyResult == ES_AUTH_RESULT_ALLOW && !allow_read_access)];
}
- (void)handleMessage:(santa::santad::event_providers::endpoint_security::Message &&)esMsg
- (void)handleMessage:(santa::Message &&)esMsg
recordEventMetrics:(void (^)(EventDisposition))recordEventMetrics {
SNTOverrideFileAccessAction overrideAction = [self.configurator overrideFileAccessAction];
@@ -834,17 +831,12 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
- (void)enable {
std::set<es_event_type_t> events = {
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_CREATE, ES_EVENT_TYPE_AUTH_EXCHANGEDATA,
ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN, ES_EVENT_TYPE_AUTH_RENAME,
ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK, ES_EVENT_TYPE_NOTIFY_EXIT,
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_COPYFILE, ES_EVENT_TYPE_AUTH_CREATE,
ES_EVENT_TYPE_AUTH_EXCHANGEDATA, ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN,
ES_EVENT_TYPE_AUTH_RENAME, ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_NOTIFY_EXIT,
};
#if HAVE_MACOS_12
if (@available(macOS 12.0, *)) {
events.insert(ES_EVENT_TYPE_AUTH_COPYFILE);
}
#endif
if (!self.isSubscribed) {
if ([super subscribe:events]) {
self.isSubscribed = true;

View File

@@ -44,8 +44,8 @@
#include "Source/santad/Logs/EndpointSecurity/MockLogger.h"
#include "Source/santad/SNTDecisionCache.h"
using santa::santad::data_layer::WatchItemPolicy;
using santa::santad::event_providers::endpoint_security::Message;
using santa::Message;
using santa::WatchItemPolicy;
extern NSString *kBadCertHash;
@@ -729,17 +729,12 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
- (void)testEnable {
std::set<es_event_type_t> expectedEventSubs = {
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_CREATE, ES_EVENT_TYPE_AUTH_EXCHANGEDATA,
ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN, ES_EVENT_TYPE_AUTH_RENAME,
ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK, ES_EVENT_TYPE_NOTIFY_EXIT,
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_COPYFILE, ES_EVENT_TYPE_AUTH_CREATE,
ES_EVENT_TYPE_AUTH_EXCHANGEDATA, ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN,
ES_EVENT_TYPE_AUTH_RENAME, ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_NOTIFY_EXIT,
};
#if HAVE_MACOS_12
if (@available(macOS 12.0, *)) {
expectedEventSubs.insert(ES_EVENT_TYPE_AUTH_COPYFILE);
}
#endif
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
EXPECT_CALL(*mockESApi, ClearCache)
.After(EXPECT_CALL(*mockESApi, Subscribe(testing::_, expectedEventSubs))
@@ -749,10 +744,14 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
id fileAccessClient = [[SNTEndpointSecurityFileAccessAuthorizer alloc]
initWithESAPI:mockESApi
metrics:nullptr
processor:santa::santad::Processor::kFileAccessAuthorizer];
processor:santa::Processor::kFileAccessAuthorizer];
[fileAccessClient enable];
for (const auto &event : expectedEventSubs) {
XCTAssertNoThrow(santa::EventTypeToString(event));
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
@@ -940,42 +939,40 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
XCTAssertFalse(targets[0].devnoIno.has_value());
}
if (@available(macOS 12.0, *)) {
{
esMsg.event_type = ES_EVENT_TYPE_AUTH_COPYFILE;
esMsg.event.copyfile.source = &testFile1;
esMsg.event.copyfile.target_dir = &testDir;
esMsg.event.copyfile.target_name = testTok;
{
esMsg.event_type = ES_EVENT_TYPE_AUTH_COPYFILE;
esMsg.event.copyfile.source = &testFile1;
esMsg.event.copyfile.target_dir = &testDir;
esMsg.event.copyfile.target_name = testTok;
esMsg.event.copyfile.target_file = nullptr;
{
esMsg.event.copyfile.target_file = nullptr;
std::vector<PathTarget> targets;
PopulatePathTargets(msg, targets);
std::vector<PathTarget> targets;
PopulatePathTargets(msg, targets);
XCTAssertEqual(targets.size(), 2);
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
XCTAssertTrue(targets[0].isReadable);
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
XCTAssertCppStringEqual(targets[1].path, dirTok);
XCTAssertFalse(targets[1].isReadable);
XCTAssertFalse(targets[1].devnoIno.has_value());
}
XCTAssertEqual(targets.size(), 2);
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
XCTAssertTrue(targets[0].isReadable);
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
XCTAssertCppStringEqual(targets[1].path, dirTok);
XCTAssertFalse(targets[1].isReadable);
XCTAssertFalse(targets[1].devnoIno.has_value());
}
{
esMsg.event.copyfile.target_file = &testFile2;
{
esMsg.event.copyfile.target_file = &testFile2;
std::vector<PathTarget> targets;
PopulatePathTargets(msg, targets);
std::vector<PathTarget> targets;
PopulatePathTargets(msg, targets);
XCTAssertEqual(targets.size(), 2);
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
XCTAssertTrue(targets[0].isReadable);
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
XCTAssertCStringEqual(targets[1].path.c_str(), testFile2.path.data);
XCTAssertFalse(targets[1].isReadable);
XCTAssertFalse(targets[1].devnoIno.has_value());
}
XCTAssertEqual(targets.size(), 2);
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
XCTAssertTrue(targets[0].isReadable);
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
XCTAssertCStringEqual(targets[1].path.c_str(), testFile2.path.data);
XCTAssertFalse(targets[1].isReadable);
XCTAssertFalse(targets[1].devnoIno.has_value());
}
}
}

View File

@@ -29,18 +29,14 @@
@interface SNTEndpointSecurityRecorder
: SNTEndpointSecurityTreeAwareClient <SNTEndpointSecurityEventHandler>
- (instancetype)
initWithESAPI:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)
esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
enricher:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
compilerController:(SNTCompilerController *)compilerController
authResultCache:
(std::shared_ptr<santa::santad::event_providers::AuthResultCache>)authResultCache
prefixTree:(std::shared_ptr<santa::common::PrefixTree<santa::common::Unit>>)prefixTree
processTree:(std::shared_ptr<santa::santad::process_tree::ProcessTree>)processTree;
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::Metrics>)metrics
logger:(std::shared_ptr<santa::Logger>)logger
enricher:(std::shared_ptr<santa::Enricher>)enricher
compilerController:(SNTCompilerController *)compilerController
authResultCache:(std::shared_ptr<santa::AuthResultCache>)authResultCache
prefixTree:(std::shared_ptr<santa::PrefixTree<santa::Unit>>)prefixTree
processTree:
(std::shared_ptr<santa::santad::process_tree::ProcessTree>)processTree;
@end

View File

@@ -17,6 +17,7 @@
#include <EndpointSecurity/EndpointSecurity.h>
#include "Source/common/Platform.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#include "Source/common/String.h"
@@ -26,15 +27,15 @@
#include "Source/santad/Metrics.h"
#include "Source/santad/ProcessTree/process_tree.h"
using santa::common::PrefixTree;
using santa::common::Unit;
using santa::santad::EventDisposition;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
using santa::santad::event_providers::endpoint_security::Enricher;
using santa::santad::event_providers::endpoint_security::Message;
using santa::santad::logs::endpoint_security::Logger;
using santa::AuthResultCache;
using santa::EndpointSecurityAPI;
using santa::EnrichedMessage;
using santa::Enricher;
using santa::EventDisposition;
using santa::Logger;
using santa::Message;
using santa::PrefixTree;
using santa::Unit;
using santa::santad::process_tree::ProcessTree;
es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
@@ -61,7 +62,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
}
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
metrics:(std::shared_ptr<santa::Metrics>)metrics
logger:(std::shared_ptr<Logger>)logger
enricher:(std::shared_ptr<Enricher>)enricher
compilerController:(SNTCompilerController *)compilerController
@@ -70,7 +71,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
processTree:(std::shared_ptr<ProcessTree>)processTree {
self = [super initWithESAPI:std::move(esApi)
metrics:std::move(metrics)
processor:santa::santad::Processor::kRecorder
processor:santa::Processor::kRecorder
processTree:std::move(processTree)];
if (self) {
_enricher = enricher;
@@ -141,7 +142,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
}
// Only log file changes that match the given regex
NSString *targetPath = santa::common::StringToNSString(targetFile->path.data);
NSString *targetPath = santa::StringToNSString(targetFile->path.data);
if (![[self.configurator fileChangesRegex]
numberOfMatchesInString:targetPath
options:0
@@ -186,16 +187,36 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
}
- (void)enable {
[super subscribe:{
ES_EVENT_TYPE_NOTIFY_CLOSE,
ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA,
ES_EVENT_TYPE_NOTIFY_EXEC,
ES_EVENT_TYPE_NOTIFY_EXIT,
ES_EVENT_TYPE_NOTIFY_FORK,
ES_EVENT_TYPE_NOTIFY_LINK,
ES_EVENT_TYPE_NOTIFY_RENAME,
ES_EVENT_TYPE_NOTIFY_UNLINK,
}];
// clang-format off
std::set<es_event_type_t> events{
ES_EVENT_TYPE_NOTIFY_CLOSE,
ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA,
ES_EVENT_TYPE_NOTIFY_EXEC,
ES_EVENT_TYPE_NOTIFY_EXIT,
ES_EVENT_TYPE_NOTIFY_FORK,
ES_EVENT_TYPE_NOTIFY_LINK,
ES_EVENT_TYPE_NOTIFY_RENAME,
ES_EVENT_TYPE_NOTIFY_UNLINK,
};
#if HAVE_MACOS_13
if (@available(macOS 13.0, *)) {
events.insert({
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN,
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT,
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN,
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT,
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK,
ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK,
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH,
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH,
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN,
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT});
}
#endif
// clang-format on
[super subscribe:events];
}
@end

View File

@@ -23,6 +23,7 @@
#include <memory>
#include <set>
#include "Source/common/Platform.h"
#include "Source/common/PrefixTree.h"
#import "Source/common/SNTConfigurator.h"
#include "Source/common/TestUtils.h"
@@ -38,15 +39,15 @@
#include "Source/santad/Metrics.h"
#import "Source/santad/SNTCompilerController.h"
using santa::common::PrefixTree;
using santa::common::Unit;
using santa::santad::EventDisposition;
using santa::santad::Processor;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
using santa::santad::event_providers::endpoint_security::Enricher;
using santa::santad::event_providers::endpoint_security::Message;
using santa::santad::logs::endpoint_security::Logger;
using santa::AuthResultCache;
using santa::EnrichedMessage;
using santa::Enricher;
using santa::EventDisposition;
using santa::Logger;
using santa::Message;
using santa::PrefixTree;
using santa::Processor;
using santa::Unit;
class MockEnricher : public Enricher {
public:
@@ -90,6 +91,24 @@ class MockLogger : public Logger {
ES_EVENT_TYPE_NOTIFY_FORK, ES_EVENT_TYPE_NOTIFY_EXIT, ES_EVENT_TYPE_NOTIFY_LINK,
ES_EVENT_TYPE_NOTIFY_RENAME, ES_EVENT_TYPE_NOTIFY_UNLINK,
};
#if HAVE_MACOS_13
if (@available(macOS 13.0, *)) {
expectedEventSubs.insert({
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN,
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT,
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK,
ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK,
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH,
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH,
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN,
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT,
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN,
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT,
});
}
#endif
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
id recorderClient = [[SNTEndpointSecurityRecorder alloc] initWithESAPI:mockESApi
@@ -100,6 +119,10 @@ class MockLogger : public Logger {
[recorderClient enable];
for (const auto &event : expectedEventSubs) {
XCTAssertNoThrow(santa::EventTypeToString(event));
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
@@ -172,7 +195,7 @@ es_file_t targetFileMissesRegex = MakeESFile("/foo/misses");
[mockCC stopMocking];
}
- (void)testHandleMessageWithCloseMappedWriteable {
- (void)testHandleEventCloseMappedWritableMatchesRegex {
#if HAVE_MACOS_13
if (@available(macOS 13.0, *)) {
// CLOSE not modified, but was_mapped_writable, should remove from cache,
@@ -199,12 +222,12 @@ es_file_t targetFileMissesRegex = MakeESFile("/foo/misses");
XCTAssertSemaTrue(*sema, 5, "Log wasn't called within expected time window");
};
[self handleMessageWithMatchCalls:YES withMissCalls:NO withBlock:testBlock];
[self handleMessageShouldLog:YES shouldRemoveFromCache:YES withBlock:testBlock];
}
#endif
}
- (void)testHandleEventCloseNotModifiedWithWasMappedWritable {
- (void)testHandleEventCloseMappedWritableMissesRegex {
#if HAVE_MACOS_13
if (@available(macOS 13.0, *)) {
// CLOSE not modified, but was_mapped_writable, remove from cache, and does not match
@@ -220,13 +243,15 @@ es_file_t targetFileMissesRegex = MakeESFile("/foo/misses");
esMsg->event.close.target = &targetFileMissesRegex;
Message msg(mockESApi, esMsg);
OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs();
XCTAssertNoThrow([recorderClient handleMessage:Message(mockESApi, esMsg)
recordEventMetrics:^(EventDisposition d) {
XCTFail("Metrics record callback should not be called here");
}]);
};
[self handleMessageWithMatchCalls:NO withMissCalls:YES withBlock:testBlock];
[self handleMessageShouldLog:NO shouldRemoveFromCache:YES withBlock:testBlock];
}
#endif
}

View File

@@ -26,10 +26,8 @@
@interface SNTEndpointSecurityTamperResistance
: SNTEndpointSecurityClient <SNTEndpointSecurityEventHandler>
- (instancetype)
initWithESAPI:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger;
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::Metrics>)metrics
logger:(std::shared_ptr<santa::Logger>)logger;
@end

View File

@@ -15,18 +15,20 @@
#import "Source/santad/EventProviders/SNTEndpointSecurityTamperResistance.h"
#include <EndpointSecurity/ESTypes.h>
#include <bsm/libbsm.h>
#include <string.h>
#include <algorithm>
#import "Source/common/SNTLogging.h"
#include "Source/santad/DataLayer/WatchItemPolicy.h"
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/Metrics.h"
using santa::santad::EventDisposition;
using santa::santad::data_layer::WatchItemPathType;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::Message;
using santa::santad::logs::endpoint_security::Logger;
using santa::EndpointSecurityAPI;
using santa::EventDisposition;
using santa::Logger;
using santa::Message;
using santa::WatchItemPathType;
static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-driver";
@@ -35,11 +37,11 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
}
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
metrics:(std::shared_ptr<santa::Metrics>)metrics
logger:(std::shared_ptr<Logger>)logger {
self = [super initWithESAPI:std::move(esApi)
metrics:std::move(metrics)
processor:santa::santad::Processor::kTamperResistance];
processor:santa::Processor::kTamperResistance];
if (self) {
_logger = logger;
@@ -85,6 +87,26 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
break;
}
case ES_EVENT_TYPE_AUTH_SIGNAL: {
// Only block signals sent to us and not from launchd.
if (audit_token_to_pid(esMsg->event.signal.target->audit_token) == getpid() &&
audit_token_to_pid(esMsg->process->audit_token) != 1) {
LOGW(@"Preventing attempt to kill Santa daemon");
result = ES_AUTH_RESULT_DENY;
}
break;
}
case ES_EVENT_TYPE_AUTH_EXEC: {
// When not running a debug build, prevent attempts to kill Santa
// by launchctl commands.
#ifndef DEBUG
result = ValidateLaunchctlExec(esMsg);
if (result == ES_AUTH_RESULT_DENY) LOGW(@"Preventing attempt to kill Santa daemon");
#endif
break;
}
case ES_EVENT_TYPE_AUTH_KEXTLOAD: {
// TODO(mlw): Since we don't package the kext anymore, we should consider removing this.
// TODO(mlw): Consider logging when kext loads are attempted.
@@ -120,15 +142,53 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
for (const auto &path : protectedPaths) {
watchPaths.push_back({path, WatchItemPathType::kLiteral});
}
watchPaths.push_back({"/Library/SystemExtensions", WatchItemPathType::kPrefix});
watchPaths.push_back({"/bin/launchctl", WatchItemPathType::kLiteral});
// Begin watching the protected set
[super muteTargetPaths:watchPaths];
[super subscribeAndClearCache:{
ES_EVENT_TYPE_AUTH_KEXTLOAD,
ES_EVENT_TYPE_AUTH_SIGNAL,
ES_EVENT_TYPE_AUTH_EXEC,
ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_AUTH_RENAME,
}];
}
es_auth_result_t ValidateLaunchctlExec(const Message &esMsg) {
es_string_token_t exec_path = esMsg->event.exec.target->executable->path;
if (strncmp(exec_path.data, "/bin/launchctl", exec_path.length) != 0) {
return ES_AUTH_RESULT_ALLOW;
}
// Ensure there are at least 2 arguments after the command
std::shared_ptr<EndpointSecurityAPI> esApi = esMsg.ESAPI();
uint32_t argCount = esApi->ExecArgCount(&esMsg->event.exec);
if (argCount < 2) {
return ES_AUTH_RESULT_ALLOW;
}
// Check for some allowed subcommands
es_string_token_t arg = esApi->ExecArg(&esMsg->event.exec, 1);
static const std::unordered_set<std::string> safe_commands{
"blame", "help", "hostinfo", "list", "plist", "print", "procinfo",
};
if (safe_commands.find(std::string(arg.data, arg.length)) != safe_commands.end()) {
return ES_AUTH_RESULT_ALLOW;
}
// Check whether com.google.santa.daemon is in the argument list.
// launchctl no longer accepts PIDs to operate on.
for (int i = 2; i < argCount; i++) {
es_string_token_t arg = esApi->ExecArg(&esMsg->event.exec, i);
if (strnstr(arg.data, "com.google.santa.daemon", arg.length) != NULL) {
return ES_AUTH_RESULT_DENY;
}
}
return ES_AUTH_RESULT_ALLOW;
}
@end

View File

@@ -31,10 +31,10 @@
#import "Source/santad/EventProviders/SNTEndpointSecurityTamperResistance.h"
#import "Source/santad/Metrics.h"
using santa::santad::EventDisposition;
using santa::santad::data_layer::WatchItemPathType;
using santa::santad::event_providers::endpoint_security::Client;
using santa::santad::event_providers::endpoint_security::Message;
using santa::Client;
using santa::EventDisposition;
using santa::Message;
using santa::WatchItemPathType;
static constexpr std::string_view kEventsDBPath = "/private/var/db/santa/events.db";
static constexpr std::string_view kRulesDBPath = "/private/var/db/santa/rules.db";
@@ -49,9 +49,8 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
- (void)testEnable {
// Ensure the client subscribes to expected event types
std::set<es_event_type_t> expectedEventSubs{
ES_EVENT_TYPE_AUTH_KEXTLOAD,
ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_AUTH_RENAME,
ES_EVENT_TYPE_AUTH_KEXTLOAD, ES_EVENT_TYPE_AUTH_SIGNAL, ES_EVENT_TYPE_AUTH_EXEC,
ES_EVENT_TYPE_AUTH_UNLINK, ES_EVENT_TYPE_AUTH_RENAME,
};
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
@@ -70,6 +69,8 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
// Setup mocks to handle muting the rules db and events db
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, testing::_, WatchItemPathType::kLiteral))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, testing::_, WatchItemPathType::kPrefix))
.WillRepeatedly(testing::Return(true));
SNTEndpointSecurityTamperResistance *tamperClient =
[[SNTEndpointSecurityTamperResistance alloc] initWithESAPI:mockESApi
@@ -79,6 +80,10 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
[mockTamperClient enable];
for (const auto &event : expectedEventSubs) {
XCTAssertNoThrow(santa::EventTypeToString(event));
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
[mockTamperClient stopMocking];
}
@@ -86,7 +91,7 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
- (void)testHandleMessage {
es_file_t file = MakeESFile("foo");
es_process_t proc = MakeESProcess(&file);
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_EXEC, &proc, ActionType::Auth);
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_LINK, &proc, ActionType::Auth);
es_file_t fileEventsDB = MakeESFile(kEventsDBPath.data());
es_file_t fileRulesDB = MakeESFile(kRulesDBPath.data());
@@ -106,6 +111,12 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
{&benignTok, ES_AUTH_RESULT_ALLOW},
};
std::map<std::pair<pid_t, pid_t>, es_auth_result_t> pidsToResult{
{{getpid(), 31838}, ES_AUTH_RESULT_DENY},
{{getpid(), 1}, ES_AUTH_RESULT_ALLOW},
{{435, 98381}, ES_AUTH_RESULT_ALLOW},
};
dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0);
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
@@ -232,6 +243,31 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
}
}
// Check SIGNAL tamper events
{
esMsg.event_type = ES_EVENT_TYPE_AUTH_SIGNAL;
for (const auto &kv : pidsToResult) {
Message msg(mockESApi, &esMsg);
es_process_t target_proc = MakeESProcess(&file);
target_proc.audit_token = MakeAuditToken(kv.first.first, 42);
esMsg.event.signal.target = &target_proc;
esMsg.process->audit_token = MakeAuditToken(kv.first.second, 42);
[mockTamperClient
handleMessage:std::move(msg)
recordEventMetrics:^(EventDisposition d) {
XCTAssertEqual(d, kv.second == ES_AUTH_RESULT_DENY ? EventDisposition::kProcessed
: EventDisposition::kDropped);
dispatch_semaphore_signal(semaMetrics);
}];
XCTAssertSemaTrue(semaMetrics, 5, "Metrics not recorded within expected window");
XCTAssertEqual(gotAuthResult, kv.second);
XCTAssertEqual(gotCachable, kv.second == ES_AUTH_RESULT_ALLOW);
}
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
XCTAssertTrue(OCMVerifyAll(mockTamperClient));

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