Compare commits

...

68 Commits

Author SHA1 Message Date
Matt W
5307bd9b7f Fix precedence for static rule evaluation, update santactl fileinfo output. (#1100) 2023-05-18 15:05:23 -04:00
Matt W
0622e6de71 Handle database downgrade scenarios gracefully (#1099) 2023-05-17 04:31:40 +02:00
Russell Hancox
e7c32ae87d Update SECURITY.md (#1098) 2023-05-12 10:30:58 -04:00
Matt W
deaf3a638c Add new rule type for Signing IDs (#1090)
* WIP: Signing ID rules

* WIP: More work supporting signing ID rules

* Expanded exec controller tests for signing ID and team ID

* wip all current tests now pass

* Added integration tests

* Branch cleanup

* Update protobuf tests for signing id reason types

* Remove old commented out code

---------

Co-authored-by: Russell Hancox <russell@hancox.us>
2023-05-12 09:22:46 -04:00
Matt W
8a7f1142a8 Stop unmuting the default mute set unnecessarily. (#1095)
* Stop unmuting the default mute set unnecessarily.

* lint

* Added note to docs explaining operations from default mute set binaries aren't logged
2023-05-10 09:07:13 -04:00
Matt W
c180205059 Return unique_ptr from Enrich instead of shared_ptr (#1093) 2023-05-08 10:55:38 -04:00
Matt W
337df0aa31 Don't establish the FAA client pre-macOS 13 (#1091)
* Don't establish the FAA client pre-macOS 13

* Only watch FAA keys on macOS 13 and newer
2023-05-05 15:33:34 -04:00
Russell Hancox
e2b099aa50 santactl/rule: Fix --path argument (#1089)
Fixes #1088
2023-05-04 17:57:59 -04:00
Pete Markowsky
fc4e29f34c Docs: Added instructions for how to use config-overrides.plist (#1077)
* Added instructions for how to use config-overrides

---------

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2023-05-01 16:16:11 -04:00
Matt W
bf3b6bc6e2 Inject additional dependencies into the serializers (#1078)
* Injects dependecies for decision cache and client mode lookup

* Fix up tests

* Stored client mode at decision time. Remove clientMode func injection.

* PR Feedback, group property members
2023-05-01 15:13:54 -04:00
Matt W
b810fc81e1 Add support to file monitoring config to invert process exceptions (#1083)
* Add support to file monitoring config to invert process exceptions

* Update docs

* Added link to github issue
2023-05-01 15:04:40 -04:00
Matt W
3b3aa999c5 Switch SNTEventState to uint64_t, reposition flag values and masks (#1086) 2023-05-01 14:37:11 -04:00
Faizan
59428f3be3 docs: Fix documentation for clean sync field in the preflight request. (#1082)
The 'request_clean_sync' field is set here: https://github.com/google/santa/blob/main/Source/santasyncservice/SNTSyncPreflight.m#L76
The constant is defined here: https://github.com/google/santa/blob/main/Source/common/SNTSyncConstants.m#L27
2023-04-27 23:38:44 -04:00
Jason McCandless
ae6451a9b2 docs: Clarify that execution_time, file_bundle_hash_millis and quarantine_timestamp are float64 (#1080) 2023-04-27 18:54:02 -04:00
Russell Hancox
feac080fa7 sync: Permit XSRF header between sync stages/sessions (#1081) 2023-04-27 10:52:35 -04:00
Nick Gregory
d0f2a0ac4d One more TSAN fix (#1079) 2023-04-26 17:30:06 +02:00
Pete Markowsky
7fc06ea9d8 Make the sync client content encoding a tunable (#1076)
Make the sync client content encoding a tunable.

This makes the sync client's content encoding a tunable so that it can be
compatible with more sync servers.

Removed the "backwards compatibility" config option.

---------

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2023-04-24 15:00:29 +02:00
Russell Hancox
1dfeeac936 README: Add more badges (#1075) 2023-04-21 09:54:33 -04:00
Matt W
ac9b5d9399 Cache flush metrics (#1074)
* Added a reason enum when flushing auth result cache

* Set metrics when auth result cache is flushed.
2023-04-20 16:47:06 -04:00
Matt W
7f3f1c5448 Process unmount events first (#1073) 2023-04-19 11:13:13 -04:00
Russell Hancox
46efd6893f config: Add EnableSilentTTYMode key to disable TTY notifications. (#1072)
Fixes #1067
2023-04-19 10:38:24 -04:00
Matt W
50232578d6 Fix string length issues (#1070) 2023-04-13 10:03:52 -04:00
Russell Hancox
d83be03a20 sync: Add more complete XSSI prefix to be stripped. (#1068)
Sync will try stripping both the new longer prefix and the existing short prefix if the response data begins with either. This should have no impact on existing sync servers but will allow sync servers in the future to use the longer prefix if they wish.
2023-04-07 15:27:41 -04:00
Russell Hancox
119b29b534 GUI: Device event window, handle empty remount args (#1066) 2023-04-05 16:34:05 -04:00
Matt W
be87b3eaf2 Change types of repeated args and envs fields (#1063)
* Change types of repeated args and envs fields

* Update args and env testdata strings to base64

* Remove whitespace
2023-03-31 13:18:09 -04:00
Russell Hancox
0fe672817e sync: Fix case of empty header name (#1062) 2023-03-28 11:50:11 -04:00
Russell Hancox
c3b2fbf512 sync: Allow server to override the header for transmitting XSRF tokens (#1060)
This change allows a sync server to change the header that Santa will use to send XSRF tokens on subsequent requests by putting the header name in the  header.
2023-03-27 18:11:11 -04:00
Matt W
2984d98cb9 Document SigningID and PlatformBinary exception keys (#1059)
* Document SigningID and PlatformBinary exception keys

* Minor spacing
2023-03-25 11:34:06 -04:00
Nick Gregory
5295faef0e Fix a couple last TSAN failures (#1056)
* Skip testHandleMessage when testing with tsan

* fix other 2 tsan failures

* change action_env->test_env in bazelrc for sanitizers

* revert Source/santactl/BUILD formatting
2023-03-23 11:11:29 -04:00
Liam Nicholson
0209344f62 santad: Fix SD Card Block not operating on Internal SD Card Readers (#1055) 2023-03-22 17:54:11 -04:00
Matt W
53ca5eb811 Support filesystem monitoring config embedded in main Santa config (#1054)
* Allow setting file access policy in main Santa config

* Add some tests
2023-03-20 16:47:34 -04:00
Matt W
33c7aab9f1 Basic rate limiting for File Access Authorizer (#1053)
* WIP basic rate limiting support

* WIP added basic metrics when rate limited

* Hookup new metrics

* Cleanup old TODO

* PR feedback, update comments
2023-03-20 15:58:49 -04:00
Pål-Magnus Slåtto
f6d837ac31 chore(ci): Upgrade workflows to non-deprecated runtimes (#1052) 2023-03-15 09:42:16 -04:00
Matt W
5e0a383662 Properly report "file access client enabled" metrics (#1051) 2023-03-14 15:01:03 -04:00
Russell Hancox
8055b451bb Config: Ignore static rules with an invalid identifier (#1049) 2023-03-07 10:33:13 -05:00
Russell Hancox
c5e7736eef santactl/rule: Validate identifier is a valid SHA-256 for binary/cert rules (#1045)
Previously validation only applied when using the --sha256 flag, now it applies to --identifier too unless adding a team ID rule. The validation is also a bit more robust.

Fixes #1044
2023-03-01 13:44:44 -05:00
Matt W
61558048c0 Add basic metrics to report when the FAM client is enabled (#1043) 2023-02-17 11:57:18 -05:00
Matt W
cf0e3fd3db Add support for platform binary to process exceptions (#1041)
* Add support for platform bianry to process exceptions

* Fun with bool types
2023-02-17 11:30:46 -05:00
Matt W
15519c6de8 Clear ES cache when watch items change (#1042) 2023-02-17 11:04:08 -05:00
Pete Markowsky
a415679980 Fix sync protocol diagram. (#1037) 2023-02-08 16:13:08 -05:00
Nick Gregory
27ae60e265 Small test fixes to make sanitizers happy (#1030)
* Small test fixes to make sanitizers happy

* lint

* missing authclient

* new MockEndpointSecurityAPI per subtest
2023-02-06 20:16:22 +00:00
Matt W
29a50f072c Report log type in santactl status (#1036)
* Report log type in santactl status

* Remove unnecessary fallback case
2023-02-06 14:59:42 -05:00
Matt W
a97e82e316 Replace SNTDecisionCache dictionary with SantaCache (#1034)
* Replace SNTDecisionCache dictionary with SantaCache

* PR feedback. Fix tests.
2023-02-03 15:58:53 -05:00
Russell Hancox
532120ac02 Configurator: Return an unsafe_unretained pointer to avoid needless retain/release (#1035) 2023-02-03 15:55:15 -05:00
Russell Hancox
ec934854fc santactl & syncservice: Use synchronousRemoteObjectProxy where it makes sense (#1033) 2023-02-03 14:31:37 -05:00
Matt W
ad0e2abdac Restart daemon on log type change (#1031)
* WIP register for event log type changes. Flush metrics.

* Add Flush to writer interface. Flush logger on log type change.

* Standardize non-thread-safe method names
2023-02-03 11:04:57 -05:00
Matt W
dc11ea6534 Rework timeout handling in metrics HTTP writer (#1029)
* Change HTTP writer to use session config timeouts

* Remove unnecessary block variable

* Fix tests

* Revert serializer changes for now

* Remove setting timeoutIntervalForRequest
2023-02-02 10:58:28 -05:00
Matt W
3acf3c1d00 Use cached sizes when serializing (#1028) 2023-01-30 16:08:38 -05:00
Matt W
41bc3d2542 Perf: Translocate cache, reserve proto repeated fields (#1027)
* Translocate cache, reserve proto repeated fields

* Remove copy/paste
2023-01-30 12:18:32 -05:00
Pete Markowsky
45a5d4e800 Fix: Rewrite the SNTMetricHTTPWriter to avoid potential stack corruption (#1019)
* Updated the SNTMetricHTTPWriter to use a for loop to prevent crashes caused by writing to stop.

* Make requests serial again.

* Fix the typo,  I just pushed.

* Ensure we only lookup the timeout value once.

* Make SNTConfigurator assignment only happen once.
2023-01-30 11:53:26 -05:00
Matt W
82bd981f31 Fix team ID and signing ID checks (#1026)
* Fix policy checks with missing team/signing ids

* Update docs to clarify how symbolic links are handled
2023-01-30 09:14:27 -05:00
Russell Hancox
6480d9c99b docs: fix width of sidebar on larger windows (#1025) 2023-01-27 15:38:46 -05:00
Henry S
7e963080b3 add updated description (#1023)
Zentral has gained many more Santa-specific workflows since adding to this section in 2017. The updated description takes this into account.
2023-01-27 15:38:14 -05:00
Matt W
e58cd7d125 Remove Default column (#1024) 2023-01-27 15:28:31 -05:00
Russell Hancox
db597e413b docs: Support wider pages, fix syntax highlighting of plist (#1022) 2023-01-27 15:18:45 -05:00
Matt W
78f46896d5 Try with more vertical space (#1021) 2023-01-27 14:37:02 -05:00
Matt W
cc0742dbfb Fsmon docs table width (#1020)
* markdown spaces lol

* markdown vertical spaces lol

* more spaces why not
2023-01-27 14:32:58 -05:00
Matt W
9c2f76af72 Initial docs for file access auth feature (#1017)
* Initial docs for file access auth feature

* Apply suggestions from code review

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

* Apply suggestions from code review

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

* Apply suggestions from code review

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

* Updates based on PR feedback

---------

Co-authored-by: Kathryn Hancox <44557882+kathancox@users.noreply.github.com>
2023-01-27 14:08:34 -05:00
Matt W
a3ed5ccb40 Log type metrics (#1018)
* Add event log type to metrics

* lint

* PR Feedback
2023-01-27 10:22:09 -05:00
Nick Gregory
b4149816c7 Add new continuous test run with various sanitizers (#1016)
* continuous tests with sanitizer matrix

* dyld insert lib

* remove msan config and upload logs
2023-01-26 16:00:47 -05:00
Matt W
2313d6338d Remove extra expectation in test (#1015) 2023-01-26 11:42:14 -05:00
Russell Hancox
414fbff721 Project: Fix module maps for swift libraries and their dependencies (#1014) 2023-01-26 09:15:30 -05:00
Matt W
5a2e42e9b4 Reduce calls into configurator (#1013) 2023-01-25 16:51:13 -05:00
Matt W
f8d1b2e880 Reduce proto warning severity (#1012) 2023-01-25 14:37:00 -05:00
Matt W
5f4d2a92fc Ensure watch item names conform to naming requirements (#1011)
* Ensure watch item names conform to naming requirements

* Only compile regex once
2023-01-25 13:27:27 -05:00
Russell Hancox
4ccffdca01 GUI: Migrate DeviceMessageWindow to SwiftUI (#1010) 2023-01-25 12:16:31 -05:00
Nick Gregory
e60bbe1b55 shadow rules_python for fuzzing (#1009) 2023-01-23 11:11:48 -05:00
Russell Hancox
eee2149439 GUI: Re-write AboutWindow view in SwiftUI (#1007) 2023-01-20 13:43:50 -05:00
155 changed files with 3593 additions and 1540 deletions

View File

@@ -9,17 +9,34 @@ build --cxxopt=-std=c++17
build --copt=-DSANTA_OPEN_SOURCE=1
build --cxxopt=-DSANTA_OPEN_SOURCE=1
build:asan --strip=never
build:asan --copt="-Wno-macro-redefined"
build:asan --copt="-D_FORTIFY_SOURCE=0"
build:asan --copt="-O1"
build:asan --copt="-fno-omit-frame-pointer"
# Many config options for sanitizers pulled from
# https://github.com/protocolbuffers/protobuf/blob/main/.bazelrc
build:san-common --strip=never
build:san-common --copt="-Wno-macro-redefined"
build:san-common --copt="-D_FORTIFY_SOURCE=0"
build:san-common --copt="-O1"
build:san-common --copt="-fno-omit-frame-pointer"
build:asan --config=san-common
build:asan --copt="-fsanitize=address"
build:asan --copt="-DADDRESS_SANITIZER"
build:asan --linkopt="-fsanitize=address"
build:asan --test_env="ASAN_OPTIONS=log_path=/tmp/san_out"
build:fuzz --copt="-Wno-macro-redefined"
build:fuzz --copt="-D_FORTIFY_SOURCE=0"
build:tsan --config=san-common
build:tsan --copt="-fsanitize=thread"
build:tsan --copt="-DTHREAD_SANITIZER=1"
build:tsan --linkopt="-fsanitize=thread"
build:tsan --test_env="TSAN_OPTIONS=log_path=/tmp/san_out:halt_on_error=true"
build:ubsan --config=san-common
build:ubsan --copt="-fsanitize=undefined"
build:ubsan --copt="-DUNDEFINED_SANITIZER=1"
build:ubsan --copt="-fno-sanitize=function" --copt="-fno-sanitize=vptr"
build:ubsan --linkopt="-fsanitize=undefined"
build:ubsan --test_env="UBSAN_OPTIONS=log_path=/tmp/san_out"
build:fuzz --config=san-common
build:fuzz --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
build:fuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
build:fuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan

View File

@@ -16,7 +16,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Run linters
run: ./Testing/lint.sh
@@ -27,7 +27,7 @@ jobs:
os: [macos-11, macos-12]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build Userspace
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=adhoc
@@ -38,14 +38,14 @@ jobs:
os: [macos-11, macos-12]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: 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
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Generate test coverage
run: sh ./generate_cov.sh
- name: Coveralls

View File

@@ -8,6 +8,6 @@ jobs:
preqs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Checks for flaky tests
run: bazel test --test_strategy=exclusive --test_output=errors --runs_per_test 50 -t- :unit_tests --define=SANTA_BUILD_TYPE=adhoc

View File

@@ -6,7 +6,7 @@ jobs:
start_vm:
runs-on: e2e-host
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Start VM
run: python3 Testing/integration/actions/start_vm.py macOS_12.bundle.tar.gz
@@ -15,7 +15,7 @@ jobs:
env:
VM_PASSWORD: ${{ secrets.VM_PASSWORD }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install configuration profile
run: bazel run //Testing/integration:install_profile -- Testing/integration/configs/default.mobileconfig
- name: Add homebrew to PATH

View File

@@ -9,14 +9,14 @@ jobs:
start_vm:
runs-on: e2e-host
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Start VM
run: python3 Testing/integration/actions/start_vm.py macOS_13.bundle.tar.gz
fuzz:
runs-on: e2e-vm
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup libfuzzer
run: Fuzzing/install_libclang_fuzzer.sh
- name: Fuzz

30
.github/workflows/sanitizers.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: sanitizers
on:
schedule:
- cron: '0 16 * * *'
workflow_dispatch:
jobs:
test:
runs-on: macos-latest
strategy:
matrix:
sanitizer: [asan, tsan, ubsan]
steps:
- uses: actions/checkout@v3
- name: ${{ matrix.sanitizer }}
run: |
CLANG_VERSION=$(clang --version | head -n 1 | cut -d' ' -f 4)
DYLIB_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/${CLANG_VERSION}/lib/darwin/libclang_rt.${{ matrix.sanitizer }}_osx_dynamic.dylib"
bazel test --config=${{ matrix.sanitizer }} \
--test_strategy=exclusive --test_output=all \
--test_env=DYLD_INSERT_LIBRARIES=${DYLIB_PATH} \
--runs_per_test 5 -t- :unit_tests \
--define=SANTA_BUILD_TYPE=adhoc
- name: Upload logs
uses: actions/upload-artifact@v1
if: failure()
with:
name: logs
path: /tmp/san_out*

View File

@@ -1,4 +1,10 @@
# Santa [![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml)
# 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)
[![latest release date](https://img.shields.io/github/release-date/google/santa.svg)](https://github.com/google/santa/releases/latest)
[![downloads](https://img.shields.io/github/downloads/google/santa/latest/total)](https://github.com/google/santa/releases/latest)
<p align="center">
<img src="https://raw.githubusercontent.com/google/santa/main/Source/gui/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />

View File

@@ -1,12 +1,14 @@
# Reporting a Vulnerability
If you believe you have found a security vulnerability, we would appreciate private disclosure
so that we can work on a fix before disclosure. Any vulnerabilities reported to us will be
If you believe you have found a security vulnerability, we would appreciate a private report
so that we can work on and release a fix before public disclosure. Any vulnerabilities reported to us will be
disclosed publicly either when a new version with fixes is released or 90 days has passed,
whichever comes first.
To report vulnerabilities to us privately, please e-mail `santa-team@google.com`.
If you want to encrypt your e-mail, you can use our GPG key `0x92AFE41DAB49BBB6`
available on keyserver.ubuntu.com:
To report vulnerabilities to us privately, either:
`gpg --keyserver keyserver.ubuntu.com --recv-key 0x92AFE41DAB49BBB6`
1) Report the vulnerability [through GitHub](https://github.com/google/santa/security/advisories/new).
2) E-mail `santa-team@google.com`. If you want to encrypt your e-mail, you can use our GPG key `0x92AFE41DAB49BBB6` available on keyserver.ubuntu.com:
`gpg --keyserver keyserver.ubuntu.com --recv-key 0x92AFE41DAB49BBB6`

View File

@@ -69,6 +69,11 @@ objc_library(
hdrs = ["Platform.h"],
)
objc_library(
name = "String",
hdrs = ["String.h"],
)
objc_library(
name = "SantaVnodeHash",
srcs = ["SantaVnodeHash.mm"],
@@ -119,6 +124,10 @@ objc_library(
name = "SNTDeviceEvent",
srcs = ["SNTDeviceEvent.m"],
hdrs = ["SNTDeviceEvent.h"],
module_name = "santa_common_SNTDeviceEvent",
sdk_frameworks = [
"Foundation",
],
deps = [
":SNTCommonEnums",
],
@@ -133,6 +142,10 @@ objc_library(
name = "SNTConfigurator",
srcs = ["SNTConfigurator.m"],
hdrs = ["SNTConfigurator.h"],
module_name = "santa_common_SNTConfigurator",
sdk_frameworks = [
"Foundation",
],
deps = [
":SNTCommonEnums",
":SNTRule",
@@ -200,6 +213,9 @@ objc_library(
name = "SNTRule",
srcs = ["SNTRule.m"],
hdrs = ["SNTRule.h"],
sdk_frameworks = [
"Foundation",
],
deps = [
":SNTCommonEnums",
":SNTSyncConstants",
@@ -231,13 +247,19 @@ objc_library(
name = "SNTSyncConstants",
srcs = ["SNTSyncConstants.m"],
hdrs = ["SNTSyncConstants.h"],
sdk_frameworks = [
"Foundation",
],
)
objc_library(
name = "SNTSystemInfo",
srcs = ["SNTSystemInfo.m"],
hdrs = ["SNTSystemInfo.h"],
sdk_frameworks = ["IOKit"],
sdk_frameworks = [
"Foundation",
"IOKit",
],
)
objc_library(

View File

@@ -191,12 +191,12 @@ using santa::common::PrefixTree;
uint32_t count = 4096;
auto t = new PrefixTree<int>(count * (uint32_t)[NSUUID UUID].UUIDString.length);
NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
__block NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count; ++i) {
[UUIDs addObject:[NSUUID UUID].UUIDString];
}
__block BOOL stop = NO;
__block _Atomic BOOL stop = NO;
// Create a bunch of background noise.
dispatch_async(dispatch_get_global_queue(0, 0), ^{

View File

@@ -29,6 +29,7 @@
@property SantaVnode vnodeId;
@property SNTEventState decision;
@property SNTClientMode decisionClientMode;
@property NSString *decisionExtra;
@property NSString *sha256;
@@ -36,6 +37,7 @@
@property NSString *certCommonName;
@property NSArray<MOLCertificate *> *certChain;
@property NSString *teamID;
@property NSString *signingID;
@property NSString *quarantineURL;

View File

@@ -42,6 +42,7 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {
SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
SNTRuleTypeTeamID = 3,
SNTRuleTypeSigningID = 4,
};
typedef NS_ENUM(NSInteger, SNTRuleState) {
@@ -63,32 +64,34 @@ typedef NS_ENUM(NSInteger, SNTClientMode) {
SNTClientModeLockdown = 2,
};
typedef NS_ENUM(NSInteger, SNTEventState) {
typedef NS_ENUM(uint64_t, SNTEventState) {
// Bits 0-15 bits store non-decision types
SNTEventStateUnknown = 0,
SNTEventStateBundleBinary = 1,
// Bits 16-23 store deny decision types
SNTEventStateBlockUnknown = 1 << 16,
SNTEventStateBlockBinary = 1 << 17,
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
SNTEventStateBlockTeamID = 1 << 20,
SNTEventStateBlockLongPath = 1 << 21,
// Bits 16-39 store deny decision types
SNTEventStateBlockUnknown = 1ULL << 16,
SNTEventStateBlockBinary = 1ULL << 17,
SNTEventStateBlockCertificate = 1ULL << 18,
SNTEventStateBlockScope = 1ULL << 19,
SNTEventStateBlockTeamID = 1ULL << 20,
SNTEventStateBlockLongPath = 1ULL << 21,
SNTEventStateBlockSigningID = 1ULL << 22,
// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
SNTEventStateAllowBinary = 1 << 25,
SNTEventStateAllowCertificate = 1 << 26,
SNTEventStateAllowScope = 1 << 27,
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
SNTEventStateAllowTeamID = 1 << 31,
// Bits 40-63 store allow decision types
SNTEventStateAllowUnknown = 1ULL << 40,
SNTEventStateAllowBinary = 1ULL << 41,
SNTEventStateAllowCertificate = 1ULL << 42,
SNTEventStateAllowScope = 1ULL << 43,
SNTEventStateAllowCompiler = 1ULL << 44,
SNTEventStateAllowTransitive = 1ULL << 45,
SNTEventStateAllowPendingTransitive = 1ULL << 46,
SNTEventStateAllowTeamID = 1ULL << 47,
SNTEventStateAllowSigningID = 1ULL << 48,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
SNTEventStateAllow = 0xFF << 24
SNTEventStateBlock = 0xFFFFFFULL << 16,
SNTEventStateAllow = 0xFFFFFFULL << 40,
};
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
@@ -129,6 +132,12 @@ typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
SNTSyncStatusTypeUnknown,
};
typedef NS_ENUM(NSInteger, SNTSyncContentEncoding) {
SNTSyncContentEncodingNone,
SNTSyncContentEncodingDeflate,
SNTSyncContentEncodingGzip,
};
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeUnknown,
SNTMetricFormatTypeRawJSON,

View File

@@ -194,6 +194,13 @@
///
@property(readonly, nonatomic) SNTEventLogType eventLogType;
///
/// Returns the raw value of the EventLogType configuration key instead of being
/// converted to the SNTEventLogType enum. If the key is not set, the default log
/// type is returned.
///
@property(readonly, nonatomic) NSString *eventLogTypeRaw;
///
/// If eventLogType is set to Filelog, eventLogPath will provide the path to save logs.
/// Defaults to /var/db/santa/santa.log.
@@ -238,10 +245,20 @@
///
@property(readonly, nonatomic) float spoolDirectoryEventMaxFlushTimeSec;
///
/// If set, contains the filesystem access policy configuration.
///
/// @note: The property fileAccessPolicyPlist will be ignored if
/// fileAccessPolicy is set.
/// @note: This property is KVO compliant.
///
@property(readonly, nonatomic) NSDictionary *fileAccessPolicy;
///
/// If set, contains the path to the filesystem access policy config plist.
///
/// @note: This property is KVO compliant, but is only read once at santad startup.
/// @note: This property will be ignored if fileAccessPolicy is set.
/// @note: This property is KVO compliant.
///
@property(readonly, nonatomic) NSString *fileAccessPolicyPlist;
@@ -274,6 +291,14 @@
///
@property(readonly, nonatomic) BOOL enableSilentMode;
///
/// When silent TTY mode is enabled, Santa will not emit TTY notifications for
/// blocked processes.
///
/// Defaults to NO.
///
@property(readonly, nonatomic) BOOL enableSilentTTYMode;
///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
@@ -385,6 +410,8 @@
///
@property(nonatomic) BOOL syncCleanRequired;
#pragma mark - USB Settings
///
/// USB Mount Blocking. Defaults to false.
///
@@ -495,6 +522,12 @@
///
@property(readonly, nonatomic) BOOL enableBackwardsCompatibleContentEncoding;
///
/// If set, "santactl sync" will use the supplied "Content-Encoding", possible
/// settings include "gzip", "deflate", "none". If empty defaults to "deflate".
///
@property(readonly, nonatomic) SNTSyncContentEncoding syncClientContentEncoding;
///
/// Contains the FCM project name.
///

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTCommonEnums.h"
#include <sys/stat.h>
@@ -72,6 +73,7 @@ static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kEnableSilentModeKey = @"EnableSilentMode";
static NSString *const kEnableSilentTTYModeKey = @"EnableSilentTTYMode";
static NSString *const kAboutTextKey = @"AboutText";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
@@ -97,6 +99,7 @@ static NSString *const kSpoolDirectoryFileSizeThresholdKB = @"SpoolDirectoryFile
static NSString *const kSpoolDirectorySizeThresholdMB = @"SpoolDirectorySizeThresholdMB";
static NSString *const kSpoolDirectoryEventMaxFlushTimeSec = @"SpoolDirectoryEventMaxFlushTimeSec";
static NSString *const kFileAccessPolicy = @"FileAccessPolicy";
static NSString *const kFileAccessPolicyPlist = @"FileAccessPolicyPlist";
static NSString *const kFileAccessPolicyUpdateIntervalSec = @"FileAccessPolicyUpdateIntervalSec";
@@ -106,8 +109,7 @@ static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
static NSString *const kIgnoreOtherEndpointSecurityClients = @"IgnoreOtherEndpointSecurityClients";
static NSString *const kEnableDebugLogging = @"EnableDebugLogging";
static NSString *const kEnableBackwardsCompatibleContentEncoding =
@"EnableBackwardsCompatibleContentEncoding";
static NSString *const kClientContentEncoding = @"SyncClientContentEncoding";
static NSString *const kFCMProject = @"FCMProject";
static NSString *const kFCMEntity = @"FCMEntity";
@@ -127,7 +129,6 @@ static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
static NSString *const kEnableAllEventUploadKey = @"EnableAllEventUpload";
static NSString *const kDisableUnknownEventUploadKey = @"DisableUnknownEventUpload";
// TODO(markowsky): move these to sync server only.
static NSString *const kMetricFormat = @"MetricFormat";
static NSString *const kMetricURL = @"MetricURL";
static NSString *const kMetricExportInterval = @"MetricExportInterval";
@@ -180,6 +181,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kEnableSilentModeKey : number,
kEnableSilentTTYModeKey : number,
kAboutTextKey : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
@@ -197,6 +199,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
kClientAuthCertificateIssuerKey : string,
kClientContentEncoding : string,
kServerAuthRootsDataKey : data,
kServerAuthRootsFileKey : string,
kMachineOwnerKey : string,
@@ -211,13 +214,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kSpoolDirectoryFileSizeThresholdKB : number,
kSpoolDirectorySizeThresholdMB : number,
kSpoolDirectoryEventMaxFlushTimeSec : number,
kFileAccessPolicy : dictionary,
kFileAccessPolicyPlist : string,
kFileAccessPolicyUpdateIntervalSec : number,
kEnableMachineIDDecoration : number,
kEnableForkAndExitLogging : number,
kIgnoreOtherEndpointSecurityClients : number,
kEnableDebugLogging : number,
kEnableBackwardsCompatibleContentEncoding : number,
kFCMProject : string,
kFCMEntity : string,
kFCMAPIKey : string,
@@ -242,7 +245,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
#pragma mark Singleton retriever
+ (instancetype)configurator {
// The returned value is marked unsafe_unretained to avoid unnecessary retain/release handling.
// The object returned is guaranteed to exist for the lifetime of the process so there's no need
// to do this handling.
+ (__unsafe_unretained instancetype)configurator {
static SNTConfigurator *sharedConfigurator;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -416,6 +422,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicy {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyPlist {
return [self configStateSet];
}
@@ -452,10 +462,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
return [self configStateSet];
}
@@ -636,6 +642,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : NO;
}
- (BOOL)enableSilentTTYMode {
NSNumber *number = self.configState[kEnableSilentTTYModeKey];
return number ? [number boolValue] : NO;
}
- (NSString *)aboutText {
return self.configState[kAboutTextKey];
}
@@ -699,6 +710,20 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return self.configState[kClientAuthCertificateIssuerKey];
}
- (SNTSyncContentEncoding)syncClientContentEncoding {
NSString *contentEncoding = [self.configState[kClientContentEncoding] lowercaseString];
if ([contentEncoding isEqualToString:@"deflate"]) {
return SNTSyncContentEncodingDeflate;
} else if ([contentEncoding isEqualToString:@"gzip"]) {
return SNTSyncContentEncodingGzip;
} else if ([contentEncoding isEqualToString:@"none"]) {
return SNTSyncContentEncodingNone;
} else {
// Ensure we have the same default zlib behavior Santa's always had otherwise.
return SNTSyncContentEncodingDeflate;
}
}
- (NSData *)syncServerAuthRootsData {
return self.configState[kServerAuthRootsDataKey];
}
@@ -776,6 +801,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
- (NSString *)eventLogTypeRaw {
return self.configState[kEventLogType] ?: @"file";
}
- (NSString *)eventLogPath {
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
}
@@ -802,8 +831,17 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
: 15.0;
}
- (NSDictionary *)fileAccessPolicy {
return self.configState[kFileAccessPolicy];
}
- (NSString *)fileAccessPolicyPlist {
return self.configState[kFileAccessPolicyPlist];
// This property is ignored when kFileAccessPolicy is set
if (self.configState[kFileAccessPolicy]) {
return nil;
} else {
return self.configState[kFileAccessPolicyPlist];
}
}
- (uint32_t)fileAccessPolicyUpdateIntervalSec {
@@ -859,11 +897,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [number boolValue] || self.debugFlag;
}
- (BOOL)enableBackwardsCompatibleContentEncoding {
NSNumber *number = self.configState[kEnableBackwardsCompatibleContentEncoding];
return number ? [number boolValue] : NO;
}
- (NSString *)fcmProject {
return self.configState[kFCMProject];
}
@@ -1113,6 +1146,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
for (id rule in staticRules) {
if (![rule isKindOfClass:[NSDictionary class]]) return;
SNTRule *r = [[SNTRule alloc] initWithDictionary:rule];
if (!r) continue;
rules[r.identifier] = r;
}
self.cachedStaticRules = [rules copy];

View File

@@ -33,6 +33,15 @@
_type = type;
_customMsg = customMsg;
_timestamp = timestamp;
if (_type == SNTRuleTypeBinary || _type == SNTRuleTypeCertificate) {
NSCharacterSet *nonHex =
[[NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEF"] invertedSet];
if ([[_identifier uppercaseString] stringByTrimmingCharactersInSet:nonHex].length != 64)
return nil;
} else if (_identifier.length == 0) {
return nil;
}
}
return self;
}
@@ -55,52 +64,54 @@
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) return nil;
self = [super init];
if (self) {
_identifier = dict[kRuleIdentifier];
if (![_identifier isKindOfClass:[NSString class]] || !_identifier.length) {
_identifier = dict[kRuleSHA256];
}
if (![_identifier isKindOfClass:[NSString class]] || !_identifier.length) return nil;
NSString *policyString = dict[kRulePolicy];
if (![policyString isKindOfClass:[NSString class]]) return nil;
if ([policyString isEqual:kRulePolicyAllowlist] ||
[policyString isEqual:kRulePolicyAllowlistDeprecated]) {
_state = SNTRuleStateAllow;
} else if ([policyString isEqual:kRulePolicyAllowlistCompiler] ||
[policyString isEqual:kRulePolicyAllowlistCompilerDeprecated]) {
_state = SNTRuleStateAllowCompiler;
} else if ([policyString isEqual:kRulePolicyBlocklist] ||
[policyString isEqual:kRulePolicyBlocklistDeprecated]) {
_state = SNTRuleStateBlock;
} else if ([policyString isEqual:kRulePolicySilentBlocklist] ||
[policyString isEqual:kRulePolicySilentBlocklistDeprecated]) {
_state = SNTRuleStateSilentBlock;
} else if ([policyString isEqual:kRulePolicyRemove]) {
_state = SNTRuleStateRemove;
} else {
return nil;
}
NSString *ruleTypeString = dict[kRuleType];
if (![ruleTypeString isKindOfClass:[NSString class]]) return nil;
if ([ruleTypeString isEqual:kRuleTypeBinary]) {
_type = SNTRuleTypeBinary;
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
_type = SNTRuleTypeCertificate;
} else if ([ruleTypeString isEqual:kRuleTypeTeamID]) {
_type = SNTRuleTypeTeamID;
} else {
return nil;
}
NSString *customMsg = dict[kRuleCustomMsg];
if ([customMsg isKindOfClass:[NSString class]] && customMsg.length) {
_customMsg = customMsg;
}
NSString *identifier = dict[kRuleIdentifier];
if (![identifier isKindOfClass:[NSString class]] || !identifier.length) {
identifier = dict[kRuleSHA256];
}
return self;
if (![identifier isKindOfClass:[NSString class]] || !identifier.length) return nil;
NSString *policyString = dict[kRulePolicy];
SNTRuleState state;
if (![policyString isKindOfClass:[NSString class]]) return nil;
if ([policyString isEqual:kRulePolicyAllowlist] ||
[policyString isEqual:kRulePolicyAllowlistDeprecated]) {
state = SNTRuleStateAllow;
} else if ([policyString isEqual:kRulePolicyAllowlistCompiler] ||
[policyString isEqual:kRulePolicyAllowlistCompilerDeprecated]) {
state = SNTRuleStateAllowCompiler;
} else if ([policyString isEqual:kRulePolicyBlocklist] ||
[policyString isEqual:kRulePolicyBlocklistDeprecated]) {
state = SNTRuleStateBlock;
} else if ([policyString isEqual:kRulePolicySilentBlocklist] ||
[policyString isEqual:kRulePolicySilentBlocklistDeprecated]) {
state = SNTRuleStateSilentBlock;
} else if ([policyString isEqual:kRulePolicyRemove]) {
state = SNTRuleStateRemove;
} else {
return nil;
}
NSString *ruleTypeString = dict[kRuleType];
SNTRuleType type;
if (![ruleTypeString isKindOfClass:[NSString class]]) return nil;
if ([ruleTypeString isEqual:kRuleTypeBinary]) {
type = SNTRuleTypeBinary;
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
type = SNTRuleTypeCertificate;
} else if ([ruleTypeString isEqual:kRuleTypeTeamID]) {
type = SNTRuleTypeTeamID;
} else if ([ruleTypeString isEqual:kRuleTypeSigningID]) {
type = SNTRuleTypeSigningID;
} else {
return nil;
}
NSString *customMsg = dict[kRuleCustomMsg];
if (![customMsg isKindOfClass:[NSString class]] || customMsg.length == 0) {
customMsg = nil;
}
return [self initWithIdentifier:identifier state:state type:type customMsg:customMsg];
}
#pragma mark NSSecureCoding

View File

@@ -25,22 +25,24 @@
SNTRule *sut;
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"some-sort-of-identifier",
@"identifier" : @"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670",
@"policy" : @"ALLOWLIST",
@"rule_type" : @"BINARY",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqualObjects(sut.identifier,
@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670");
XCTAssertEqual(sut.type, SNTRuleTypeBinary);
XCTAssertEqual(sut.state, SNTRuleStateAllow);
sut = [[SNTRule alloc] initWithDictionary:@{
@"sha256" : @"some-sort-of-identifier",
@"sha256" : @"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670",
@"policy" : @"BLOCKLIST",
@"rule_type" : @"CERTIFICATE",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqualObjects(sut.identifier,
@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670");
XCTAssertEqual(sut.type, SNTRuleTypeCertificate);
XCTAssertEqual(sut.state, SNTRuleStateBlock);
@@ -55,12 +57,13 @@
XCTAssertEqual(sut.state, SNTRuleStateSilentBlock);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"some-sort-of-identifier",
@"identifier" : @"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670",
@"policy" : @"ALLOWLIST_COMPILER",
@"rule_type" : @"BINARY",
}];
XCTAssertNotNil(sut);
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
XCTAssertEqualObjects(sut.identifier,
@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670");
XCTAssertEqual(sut.type, SNTRuleTypeBinary);
XCTAssertEqual(sut.state, SNTRuleStateAllowCompiler);
@@ -94,12 +97,19 @@
XCTAssertNil(sut);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"an-identifier",
@"identifier" : @"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670",
}];
XCTAssertNil(sut);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"an-identifier",
@"policy" : @"ALLOWLIST",
@"rule_type" : @"BINARY",
}];
XCTAssertNil(sut);
sut = [[SNTRule alloc] initWithDictionary:@{
@"identifier" : @"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670",
@"policy" : @"OTHERPOLICY",
@"rule_type" : @"BINARY",
}];

View File

@@ -100,6 +100,11 @@
///
@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.
///

View File

@@ -50,6 +50,7 @@
ENCODE(self.signingChain, @"signingChain");
ENCODE(self.teamID, @"teamID");
ENCODE(self.signingID, @"signingID");
ENCODE(self.executingUser, @"executingUser");
ENCODE(self.occurrenceDate, @"occurrenceDate");
@@ -95,10 +96,11 @@
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
_teamID = DECODE(NSString, @"teamID");
_signingID = DECODE(NSString, @"signingID");
_executingUser = DECODE(NSString, @"executingUser");
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") intValue];
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") unsignedLongLongValue];
_pid = DECODE(NSNumber, @"pid");
_ppid = DECODE(NSNumber, @"ppid");
_parentName = DECODE(NSString, @"parentName");

View File

@@ -14,7 +14,8 @@
#import <Foundation/Foundation.h>
extern NSString *const kXSRFToken;
extern NSString *const kDefaultXSRFTokenHeader;
extern NSString *const kXSRFTokenHeader;
extern NSString *const kSerialNumber;
extern NSString *const kHostname;
@@ -41,6 +42,7 @@ extern NSString *const kCertificateRuleCount;
extern NSString *const kCompilerRuleCount;
extern NSString *const kTransitiveRuleCount;
extern NSString *const kTeamIDRuleCount;
extern NSString *const kSigningIDRuleCount;
extern NSString *const kFullSyncInterval;
extern NSString *const kFCMToken;
extern NSString *const kFCMFullSyncInterval;
@@ -65,11 +67,13 @@ extern NSString *const kDecisionAllowBinary;
extern NSString *const kDecisionAllowCertificate;
extern NSString *const kDecisionAllowScope;
extern NSString *const kDecisionAllowTeamID;
extern NSString *const kDecisionAllowSigningID;
extern NSString *const kDecisionBlockUnknown;
extern NSString *const kDecisionBlockBinary;
extern NSString *const kDecisionBlockCertificate;
extern NSString *const kDecisionBlockScope;
extern NSString *const kDecisionBlockTeamID;
extern NSString *const kDecisionBlockSigningID;
extern NSString *const kDecisionUnknown;
extern NSString *const kDecisionBundleBinary;
extern NSString *const kLoggedInUsers;
@@ -94,6 +98,7 @@ extern NSString *const kCertOU;
extern NSString *const kCertValidFrom;
extern NSString *const kCertValidUntil;
extern NSString *const kTeamID;
extern NSString *const kSigningID;
extern NSString *const kQuarantineDataURL;
extern NSString *const kQuarantineRefererURL;
extern NSString *const kQuarantineTimestamp;
@@ -117,6 +122,7 @@ extern NSString *const kRuleType;
extern NSString *const kRuleTypeBinary;
extern NSString *const kRuleTypeCertificate;
extern NSString *const kRuleTypeTeamID;
extern NSString *const kRuleTypeSigningID;
extern NSString *const kRuleCustomMsg;
extern NSString *const kCursor;

View File

@@ -14,7 +14,8 @@
#import "Source/common/SNTSyncConstants.h"
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
NSString *const kDefaultXSRFTokenHeader = @"X-XSRF-TOKEN";
NSString *const kXSRFTokenHeader = @"X-XSRF-TOKEN-HEADER";
NSString *const kSerialNumber = @"serial_num";
NSString *const kHostname = @"hostname";
@@ -41,6 +42,7 @@ NSString *const kCertificateRuleCount = @"certificate_rule_count";
NSString *const kCompilerRuleCount = @"compiler_rule_count";
NSString *const kTransitiveRuleCount = @"transitive_rule_count";
NSString *const kTeamIDRuleCount = @"teamid_rule_count";
NSString *const kSigningIDRuleCount = @"signingid_rule_count";
NSString *const kFullSyncInterval = @"full_sync_interval";
NSString *const kFCMToken = @"fcm_token";
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
@@ -66,11 +68,13 @@ NSString *const kDecisionAllowBinary = @"ALLOW_BINARY";
NSString *const kDecisionAllowCertificate = @"ALLOW_CERTIFICATE";
NSString *const kDecisionAllowScope = @"ALLOW_SCOPE";
NSString *const kDecisionAllowTeamID = @"ALLOW_TEAMID";
NSString *const kDecisionAllowSigningID = @"ALLOW_SIGNINGID";
NSString *const kDecisionBlockUnknown = @"BLOCK_UNKNOWN";
NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
NSString *const kDecisionBlockTeamID = @"BLOCK_TEAMID";
NSString *const kDecisionBlockSigningID = @"BLOCK_SIGNINGID";
NSString *const kDecisionUnknown = @"UNKNOWN";
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
NSString *const kLoggedInUsers = @"logged_in_users";
@@ -95,6 +99,7 @@ NSString *const kCertOU = @"ou";
NSString *const kCertValidFrom = @"valid_from";
NSString *const kCertValidUntil = @"valid_until";
NSString *const kTeamID = @"team_id";
NSString *const kSigningID = @"signing_id";
NSString *const kQuarantineDataURL = @"quarantine_data_url";
NSString *const kQuarantineRefererURL = @"quarantine_referer_url";
NSString *const kQuarantineTimestamp = @"quarantine_timestamp";
@@ -118,6 +123,7 @@ NSString *const kRuleType = @"rule_type";
NSString *const kRuleTypeBinary = @"BINARY";
NSString *const kRuleTypeCertificate = @"CERTIFICATE";
NSString *const kRuleTypeTeamID = @"TEAMID";
NSString *const kRuleTypeSigningID = @"SIGNINGID";
NSString *const kRuleCustomMsg = @"custom_msg";
NSString *const kCursor = @"cursor";

View File

@@ -35,6 +35,7 @@
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
signingID:(NSString *)signingID
reply:(void (^)(SNTRule *))reply;
///

View File

@@ -37,7 +37,7 @@
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive, int64_t teamID))reply;
int64_t transitive, int64_t teamID, int64_t signingID))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)staticRuleCount:(void (^)(int64_t count))reply;
@@ -57,6 +57,7 @@
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
signingID:(NSString *)signingID
reply:(void (^)(SNTEventState))reply;
///

View File

@@ -28,12 +28,16 @@ typedef struct SantaVnode {
return fsid == rhs.fsid && fileid == rhs.fileid;
}
static inline SantaVnode VnodeForFile(const es_file_t *es_file) {
static inline SantaVnode VnodeForFile(const struct stat &sb) {
return SantaVnode{
.fsid = es_file->stat.st_dev,
.fileid = es_file->stat.st_ino,
.fsid = sb.st_dev,
.fileid = sb.st_ino,
};
}
static inline SantaVnode VnodeForFile(const es_file_t *es_file) {
return VnodeForFile(es_file->stat);
}
#endif
} SantaVnode;

35
Source/common/String.h Normal file
View File

@@ -0,0 +1,35 @@
/// Copyright 2023 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.
#ifndef SANTA__COMMON__STRING_H
#define SANTA__COMMON__STRING_H
#include <Foundation/Foundation.h>
#include <string>
#include <string_view>
namespace santa::common {
static inline std::string_view NSStringToUTF8StringView(NSString *str) {
return std::string_view(str.UTF8String, [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}
static inline std::string NSStringToUTF8String(NSString *str) {
return std::string(str.UTF8String, [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}
} // namespace santa::common
#endif

View File

@@ -35,6 +35,9 @@ uint64_t MachTimeToNanos(uint64_t mach_time);
// Convert nanoseconds to mach absolute time
uint64_t NanosToMachTime(uint64_t nanos);
// Add some number of nanoseconds to a given mach time and return the new result
uint64_t AddNanosecondsToMachTime(uint64_t ns, uint64_t machTime);
// Get the result of proc_pidinfo with the PROC_PIDTASKINFO flavor
std::optional<SantaTaskInfo> GetTaskInfo();

View File

@@ -39,17 +39,28 @@ static mach_timebase_info_data_t GetTimebase() {
}
uint64_t MachTimeToNanos(uint64_t mach_time) {
mach_timebase_info_data_t timebase = GetTimebase();
static mach_timebase_info_data_t timebase = GetTimebase();
return mach_time * timebase.numer / timebase.denom;
}
uint64_t NanosToMachTime(uint64_t nanos) {
mach_timebase_info_data_t timebase = GetTimebase();
static mach_timebase_info_data_t timebase = GetTimebase();
return nanos * timebase.denom / timebase.numer;
}
uint64_t AddNanosecondsToMachTime(uint64_t ns, uint64_t machTime) {
// Convert machtime to nanoseconds
uint64_t nanoTime = MachTimeToNanos(machTime);
// Add the nanosecond offset
nanoTime += ns;
// Convert back to machTime
return NanosToMachTime(nanoTime);
}
std::optional<SantaTaskInfo> GetTaskInfo() {
struct proc_taskinfo pti;

View File

@@ -87,16 +87,6 @@ es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t par
};
}
static uint64_t AddMillisToMachTime(uint64_t ms, uint64_t machTime) {
uint64_t nanoTime = MachTimeToNanos(machTime);
// Add the ms offset
nanoTime += (ms * NSEC_PER_MSEC);
// Convert back to machTime
return NanosToMachTime(nanoTime);
}
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
// Note: ES message v3 was only in betas.
if (@available(macOS 13.0, *)) {
@@ -115,7 +105,7 @@ uint32_t MaxSupportedESMessageVersionForCurrentOS() {
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 = {
.deadline = AddMillisToMachTime(future_deadline_ms, mach_absolute_time()),
.deadline = AddNanosecondsToMachTime(future_deadline_ms * NSEC_PER_MSEC, mach_absolute_time()),
.process = proc,
.action_type =
(action_type == ActionType::Notify) ? ES_ACTION_TYPE_NOTIFY : ES_ACTION_TYPE_AUTH,

View File

@@ -1,8 +1,4 @@
//
// !!! WARNING !!!
// This proto is for demonstration purposes only and will be changing.
// Do not rely on this format.
//
// Important: This schema is currently in BETA
syntax = "proto3";
@@ -235,10 +231,10 @@ message Execution {
optional FileInfo working_directory = 4;
// List of process arguments
repeated string args = 5;
repeated bytes args = 5;
// List of environment variables
repeated string envs = 6;
repeated bytes envs = 6;
// List of file descriptors
repeated FileDescriptor fds = 7;
@@ -266,6 +262,7 @@ message Execution {
REASON_TRANSITIVE = 8;
REASON_LONG_PATH = 9;
REASON_NOT_RUNNING = 10;
REASON_SIGNING_ID = 11;
}
optional Reason reason = 10;

View File

@@ -1,4 +1,5 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
load("//:helper.bzl", "santa_unit_test")
licenses(["notice"])
@@ -11,6 +12,25 @@ exports_files([
"Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-256.png",
])
swift_library(
name = "SNTAboutWindowView",
srcs = ["SNTAboutWindowView.swift"],
generates_header = 1,
deps = ["//Source/common:SNTConfigurator"],
)
swift_library(
name = "SNTDeviceMessageWindowView",
srcs = [
"SNTDeviceMessageWindowView.swift",
],
generates_header = 1,
deps = [
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
],
)
objc_library(
name = "SantaGUI_lib",
srcs = [
@@ -24,8 +44,6 @@ objc_library(
"SNTBinaryMessageWindowController.m",
"SNTDeviceMessageWindowController.h",
"SNTDeviceMessageWindowController.m",
"SNTMessageWindow.h",
"SNTMessageWindow.m",
"SNTMessageWindowController.h",
"SNTMessageWindowController.m",
"SNTNotificationManager.h",
@@ -36,8 +54,6 @@ objc_library(
"SNTNotificationManager.h",
],
data = [
"Resources/AboutWindow.xib",
"Resources/DeviceMessageWindow.xib",
"Resources/MessageWindow.xib",
],
sdk_frameworks = [
@@ -47,6 +63,8 @@ objc_library(
"UserNotifications",
],
deps = [
":SNTAboutWindowView",
":SNTDeviceMessageWindowView",
"//Source/common:SNTBlockMessage_SantaGUI",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",

View File

@@ -1,95 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
<connections>
<outlet property="aboutTextField" destination="uh6-q0-RzL" id="oGn-hV-wwc"/>
<outlet property="moreInfoButton" destination="SRu-Kf-vu5" id="Vj2-9Q-05d"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BnL-ZS-kXw">
<rect key="frame" x="199" y="140" width="83" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="UK2-2L-lPx"/>
<constraint firstAttribute="width" constant="79" id="lDf-D7-qlY"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="VVj-gU-bzy">
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uh6-q0-RzL">
<rect key="frame" x="18" y="65" width="444" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="CcT-ul-1eA">
<font key="font" metaFont="system"/>
<string key="title">Santa is an application control system for macOS.
There are no user-configurable settings.</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SRu-Kf-vu5">
<rect key="frame" x="129" y="21" width="113" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="99" id="JHv-2J-QSe"/>
</constraints>
<buttonCell key="cell" type="push" title="More Info..." bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6fe-ju-aET">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="openMoreInfoURL:" target="-2" id="dps-TN-rkS"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Udo-BY-n7e">
<rect key="frame" x="239" y="21" width="113" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="99" id="2Xc-ax-2bV"/>
</constraints>
<buttonCell key="cell" type="push" title="Dismiss" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uSw-o1-lWW">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="orderOut:" target="-1" id="6oW-zI-zn5"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Udo-BY-n7e" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" priority="900" constant="191" id="1T4-DB-Dz8"/>
<constraint firstItem="SRu-Kf-vu5" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="136" id="Ake-nU-qhW"/>
<constraint firstItem="BnL-ZS-kXw" firstAttribute="top" secondItem="se5-gp-TjO" secondAttribute="top" constant="20" symbolic="YES" id="Fj1-SG-mzF"/>
<constraint firstAttribute="bottom" secondItem="Udo-BY-n7e" secondAttribute="bottom" constant="28" id="bpF-hC-haN"/>
<constraint firstAttribute="bottom" secondItem="SRu-Kf-vu5" secondAttribute="bottom" constant="28" id="fCB-02-SEt"/>
<constraint firstItem="BnL-ZS-kXw" firstAttribute="centerX" secondItem="se5-gp-TjO" secondAttribute="centerX" id="kez-S0-6Gg"/>
<constraint firstItem="Udo-BY-n7e" firstAttribute="leading" secondItem="SRu-Kf-vu5" secondAttribute="trailing" constant="11" id="sYO-yY-w9w"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
</connections>
<point key="canvasLocation" x="323" y="317"/>
</window>
</objects>
</document>

View File

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

View File

@@ -14,11 +14,5 @@
#import <Cocoa/Cocoa.h>
@interface SNTAboutWindowController : NSWindowController
@property IBOutlet NSTextField *aboutTextField;
@property IBOutlet NSButton *moreInfoButton;
- (IBAction)openMoreInfoURL:(id)sender;
@interface SNTAboutWindowController : NSWindowController <NSWindowDelegate>
@end

View File

@@ -13,30 +13,35 @@
/// limitations under the License.
#import "Source/gui/SNTAboutWindowController.h"
#import "Source/gui/SNTAboutWindowView-Swift.h"
#import "Source/common/SNTConfigurator.h"
@implementation SNTAboutWindowController
- (instancetype)init {
return [super initWithWindowNibName:@"AboutWindow"];
- (void)showWindow:(id)sender {
[super showWindow:sender];
if (self.window) [self.window orderOut:sender];
self.window =
[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 0, 0)
styleMask:NSWindowStyleMaskClosable | NSWindowStyleMaskTitled
backing:NSBackingStoreBuffered
defer:NO];
self.window.contentViewController = [SNTAboutWindowViewFactory createWithWindow:self.window];
self.window.title = @"Santa";
self.window.delegate = self;
[self.window makeKeyAndOrderFront:nil];
[self.window center];
// Add app to Cmd+Tab and Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
}
- (void)loadWindow {
[super loadWindow];
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *aboutText = [config aboutText];
if (aboutText) {
[self.aboutTextField setStringValue:aboutText];
}
if (![config moreInfoURL]) {
[self.moreInfoButton removeFromSuperview];
}
}
- (IBAction)openMoreInfoURL:(id)sender {
[[NSWorkspace sharedWorkspace] openURL:[[SNTConfigurator configurator] moreInfoURL]];
[self close];
- (void)windowWillClose:(NSNotification *)notification {
// Remove app from Cmd+Tab and Dock.
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
}
@end

View File

@@ -0,0 +1,63 @@
import SwiftUI
import santa_common_SNTConfigurator
@objc public class SNTAboutWindowViewFactory : NSObject {
@objc public static func createWith(window: NSWindow) -> NSViewController {
return NSHostingController(rootView:SNTAboutWindowView(w:window).frame(width:400, height:200))
}
}
struct SNTAboutWindowView: View {
let w: NSWindow?
let c = SNTConfigurator()
var body: some View {
VStack(spacing:20.0) {
Text("Santa").font(Font.custom("HelveticaNeue-UltraLight", size: 34.0))
if let t = c.aboutText {
Text(t).multilineTextAlignment(.center)
} else {
Text("""
Santa is an application control system for macOS.
There are no user-configurable settings.
""").multilineTextAlignment(.center)
}
HStack {
if c.moreInfoURL?.absoluteString.isEmpty == false {
Button(action: moreInfoButton) {
Text("More Info...").frame(width: 90.0)
}
}
Button(action: dismissButton) {
Text("Dismiss").frame(width: 90.0)
}
.keyboardShortcut(.defaultAction)
}.padding(10.0)
}
}
func dismissButton() {
w?.close()
}
func moreInfoButton() {
if let u = c.moreInfoURL {
NSWorkspace.shared.open(u)
}
w?.close()
}
}
// Enable previews in Xcode.
struct SNTAboutWindow_Previews: PreviewProvider {
static var previews: some View {
SNTAboutWindowView(w: nil)
}
}

View File

@@ -58,7 +58,9 @@
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
if (!self.aboutWindowController) {
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
}
[self.aboutWindowController showWindow:self];
return NO;
}

View File

@@ -20,7 +20,6 @@
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/gui/SNTMessageWindow.h"
@interface SNTBinaryMessageWindowController ()
/// The custom message to display for this event

View File

@@ -23,10 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
///
/// Controller for a single message window.
///
@interface SNTDeviceMessageWindowController : SNTMessageWindowController
@property(weak) IBOutlet NSTextField *remountArgsLabel;
@property(weak) IBOutlet NSTextField *remountArgsTitle;
@interface SNTDeviceMessageWindowController : SNTMessageWindowController <NSWindowDelegate>
// The device event this window is for.
@property(readonly) SNTDeviceEvent *event;

View File

@@ -13,11 +13,11 @@
/// limitations under the License.
#import "Source/gui/SNTDeviceMessageWindowController.h"
#import "Source/gui/SNTDeviceMessageWindowView-Swift.h"
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/gui/SNTMessageWindow.h"
NS_ASSUME_NONNULL_BEGIN
@@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation SNTDeviceMessageWindowController
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message {
self = [super initWithWindowNibName:@"DeviceMessageWindow"];
self = [super init];
if (self) {
_event = event;
_customMessage = message;
@@ -36,12 +36,30 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (void)loadWindow {
[super loadWindow];
if (!self.event.remountArgs || [self.event.remountArgs count] <= 0) {
[self.remountArgsLabel removeFromSuperview];
[self.remountArgsTitle removeFromSuperview];
}
- (void)showWindow:(id)sender {
if (self.window) [self.window orderOut:sender];
self.window =
[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 0, 0)
styleMask:NSWindowStyleMaskClosable | NSWindowStyleMaskTitled
backing:NSBackingStoreBuffered
defer:NO];
self.window.contentViewController =
[SNTDeviceMessageWindowViewFactory createWithWindow:self.window
event:self.event
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 {

View File

@@ -0,0 +1,76 @@
import SwiftUI
import santa_common_SNTConfigurator
import santa_common_SNTDeviceEvent
@objc public class SNTDeviceMessageWindowViewFactory : NSObject {
@objc public static func createWith(window: NSWindow, event: SNTDeviceEvent, customMsg: NSAttributedString?) -> NSViewController {
return NSHostingController(rootView:SNTDeviceMessageWindowView(window:window, event:event, customMsg:customMsg).frame(width:450, height:300))
}
}
struct SNTDeviceMessageWindowView: View {
let window: NSWindow?
let event: SNTDeviceEvent?
let customMsg: NSAttributedString?
let c = SNTConfigurator()
var body: some View {
VStack(spacing:20.0) {
Text("Santa").font(Font.custom("HelveticaNeue-UltraLight", size: 34.0))
if let t = customMsg {
if #available(macOS 12.0, *) {
let a = AttributedString(t)
Text(a).multilineTextAlignment(.center).padding(15.0)
} else {
Text(t.description).multilineTextAlignment(.center).padding(15.0)
}
} else {
Text("Mounting devices is blocked")
}
HStack(spacing:5.0) {
VStack(alignment: .trailing, spacing: 8.0) {
Text("Device Name").bold()
Text("Device BSD Path").bold()
if event!.remountArgs?.count ?? 0 > 0 {
Text("Remount Mode").bold()
}
}
Spacer().frame(width: 10.0)
VStack(alignment: .leading, spacing: 8.0) {
Text(event!.mntonname)
Text(event!.mntfromname)
if event!.remountArgs?.count ?? 0 > 0 {
Text(event!.readableRemountArgs())
}
}
}
HStack {
Button(action: dismissButton) {
Text("OK").frame(width: 90.0)
}
.keyboardShortcut(.defaultAction)
}.padding(10.0)
}
}
func dismissButton() {
window?.close()
}
}
// Enable previews in Xcode.
struct SNTDeviceMessageWindowView_Previews: PreviewProvider {
static var previews: some View {
SNTDeviceMessageWindowView(window: nil, event: nil, customMsg: nil)
}
}

View File

@@ -4,7 +4,7 @@
- (void)windowDidCloseSilenceHash:(NSString *)hash;
@end
@interface SNTMessageWindowController : NSWindowController
@interface SNTMessageWindowController : NSWindowController <NSWindowDelegate>
- (IBAction)showWindow:(id)sender;
- (IBAction)closeWindow:(id)sender;

View File

@@ -1,14 +1,17 @@
#import "Source/gui/SNTMessageWindowController.h"
#import "Source/gui/SNTMessageWindow.h"
@implementation SNTMessageWindowController
- (IBAction)showWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeIn:sender];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
[self.window makeKeyAndOrderFront:sender];
[self.window center];
[NSApp activateIgnoringOtherApps:YES];
}
- (IBAction)closeWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeOut:sender];
[self windowWillClose:sender];
[self.window close];
}
- (void)windowWillClose:(NSNotification *)notification {
@@ -21,12 +24,6 @@
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
}
- (NSString *)messageHash {
[self doesNotRecognizeSelector:_cmd];
return nil;

View File

@@ -37,14 +37,14 @@ objc_library(
objc_library(
name = "santactl_lib",
srcs = [
"main.m",
"Commands/SNTCommandFileInfo.m",
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandVersion.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetrics.m",
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandSync.m",
"Commands/SNTCommandVersion.m",
"main.m",
] + select({
"//:opt_build": [],
"//conditions:default": [

View File

@@ -40,27 +40,27 @@ REGISTER_COMMAND_NAME(@"checkcache")
}
+ (NSString *)shortHelpText {
return @"Prints the status of a file in the kernel cache.";
return @"Prints the status of a file in the cache.";
}
+ (NSString *)longHelpText {
return (@"Checks the in-kernel cache for desired file.\n"
return (@"Checks the cache for desired file.\n"
@"Returns 0 if successful, 1 otherwise");
}
- (void)runWithArguments:(NSArray *)arguments {
SantaVnode vnodeID = [self vnodeIDForFile:arguments.firstObject];
[[self.daemonConn remoteObjectProxy]
[[self.daemonConn synchronousRemoteObjectProxy]
checkCacheForVnodeID:vnodeID
withReply:^(SNTAction action) {
if (action == SNTActionRespondAllow) {
LOGI(@"File exists in [allowlist] kernel cache");
LOGI(@"File exists in [allowlist] cache");
exit(0);
} else if (action == SNTActionRespondDeny) {
LOGI(@"File exists in [blocklist] kernel cache");
LOGI(@"File exists in [blocklist] cache");
exit(0);
} else if (action == SNTActionRespondAllowCompiler) {
LOGI(@"File exists in [allowlist compiler] kernel cache");
LOGI(@"File exists in [allowlist compiler] cache");
exit(0);
} else if (action == SNTActionUnset) {
LOGE(@"File does not exist in cache");

View File

@@ -42,6 +42,7 @@ static NSString *const kRule = @"Rule";
static NSString *const kSigningChain = @"Signing Chain";
static NSString *const kUniversalSigningChain = @"Universal Signing Chain";
static NSString *const kTeamID = @"Team ID";
static NSString *const kSigningID = @"Signing ID";
// signing chain keys
static NSString *const kCommonName = @"Common Name";
@@ -111,6 +112,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadTimestamp;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadAgent;
@property(readonly, copy, nonatomic) SNTAttributeBlock teamID;
@property(readonly, copy, nonatomic) SNTAttributeBlock signingID;
@property(readonly, copy, nonatomic) SNTAttributeBlock type;
@property(readonly, copy, nonatomic) SNTAttributeBlock pageZero;
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
@@ -184,8 +186,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
+ (NSArray<NSString *> *)fileInfoKeys {
return @[
kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr, kDownloadReferrerURL,
kDownloadURL, kDownloadTimestamp, kDownloadAgent, kTeamID, kType, kPageZero, kCodeSigned, kRule,
kSigningChain, kUniversalSigningChain
kDownloadURL, kDownloadTimestamp, kDownloadAgent, kTeamID, kSigningID, kType, kPageZero,
kCodeSigned, kRule, kSigningChain, kUniversalSigningChain
];
}
@@ -218,6 +220,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
kSigningChain : self.signingChain,
kUniversalSigningChain : self.universalSigningChain,
kTeamID : self.teamID,
kSigningID : self.signingID,
};
_printQueue = dispatch_queue_create("com.google.santactl.print_queue", DISPATCH_QUEUE_SERIAL);
@@ -357,15 +360,34 @@ REGISTER_COMMAND_NAME(@"fileinfo")
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSError *err;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&err];
[[cmd.daemonConn remoteObjectProxy]
decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
teamID:[csc.signingInformation valueForKey:@"teamid"]
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
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];
}
}
}
[[cmd.daemonConn remoteObjectProxy] decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
teamID:teamID
signingID:signingID
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
cmd.daemonUnavailable = YES;
return kCommunicationErrorMsg;
@@ -381,6 +403,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
case SNTEventStateBlockCertificate: [output appendString:@" (Certificate)"]; break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID: [output appendString:@" (TeamID)"]; break;
case SNTEventStateAllowSigningID:
case SNTEventStateBlockSigningID: [output appendString:@" (SigningID)"]; break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
case SNTEventStateAllowCompiler: [output appendString:@" (Compiler)"]; break;
@@ -473,6 +497,13 @@ REGISTER_COMMAND_NAME(@"fileinfo")
};
}
- (SNTAttributeBlock)signingID {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
return [csc.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoIdentifier];
};
}
#pragma mark -
// Entry point for the command.

View File

@@ -166,19 +166,10 @@ REGISTER_COMMAND_NAME(@"metrics")
- (void)runWithArguments:(NSArray *)arguments {
__block NSDictionary *metrics;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] metrics:^(NSDictionary *exportedMetrics) {
[[self.daemonConn synchronousRemoteObjectProxy] metrics:^(NSDictionary *exportedMetrics) {
metrics = exportedMetrics;
dispatch_group_leave(group);
}];
// Wait a maximum of 5s for metrics collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
}
metrics = [self filterMetrics:metrics withArguments:arguments];
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];

View File

@@ -60,16 +60,28 @@ REGISTER_COMMAND_NAME(@"rule")
@" Will add the hash of the file currently at that path.\n"
@" Does not work with --check. Use the fileinfo verb to check.\n"
@" the rule state of a file.\n"
@" --identifier {sha256|teamID}: identifier to add/remove/check\n"
@" --identifier {sha256|teamID|signingID}: identifier to add/remove/check\n"
@" --sha256 {sha256}: hash to add/remove/check [deprecated]\n"
@"\n"
@" Optionally:\n"
@" --teamid: add or check a team ID rule instead of binary\n"
@" --signingid: add or check a signing ID rule instead of binary (see notes)\n"
@" --certificate: add or check a certificate sha256 rule instead of binary\n"
#ifdef DEBUG
@" --force: allow manual changes even when SyncBaseUrl is set\n"
#endif
@" --message {message}: custom message\n");
@" --message {message}: custom message\n"
@"\n"
@" Notes:\n"
@" The format of `identifier` when adding/checking a `signingid` rule is:\n"
@"\n"
@" `TeamID:SigningID`\n"
@"\n"
@" Because signing IDs are controlled by the binary author, this ensures\n"
@" that the signing ID is properly scoped to a developer. For the special\n"
@" case of platform binaries, `TeamID` should be replaced with the string\n"
@" \"platform\" (e.g. `platform:SigningID`). This allows for rules\n"
@" targeting Apple-signed binaries that do not have a team ID.\n");
}
- (void)runWithArguments:(NSArray *)arguments {
@@ -116,6 +128,8 @@ REGISTER_COMMAND_NAME(@"rule")
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--teamid"] == NSOrderedSame) {
newRule.type = SNTRuleTypeTeamID;
} else if ([arg caseInsensitiveCompare:@"--signingid"] == NSOrderedSame) {
newRule.type = SNTRuleTypeSigningID;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--path requires an argument"];
@@ -131,9 +145,6 @@ REGISTER_COMMAND_NAME(@"rule")
[self printErrorUsageAndExit:@"--sha256 requires an argument"];
}
newRule.identifier = arguments[i];
if (newRule.identifier.length != 64) {
[self printErrorUsageAndExit:@"--sha256 requires a valid SHA-256 as the argument"];
}
} else if ([arg caseInsensitiveCompare:@"--message"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--message requires an argument"];
@@ -148,11 +159,6 @@ REGISTER_COMMAND_NAME(@"rule")
}
}
if (check) {
if (!newRule.identifier) return [self printErrorUsageAndExit:@"--check requires --identifier"];
return [self printStateOfRule:newRule daemonConnection:self.daemonConn];
}
if (path) {
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
if (!fi.path) {
@@ -164,10 +170,25 @@ REGISTER_COMMAND_NAME(@"rule")
} else if (newRule.type == SNTRuleTypeCertificate) {
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
newRule.identifier = cs.leafCertificate.SHA256;
} else if (newRule.type == SNTRuleTypeTeamID) {
} else if (newRule.type == SNTRuleTypeTeamID || newRule.type == SNTRuleTypeSigningID) {
// noop
}
}
if (newRule.type == SNTRuleTypeBinary || newRule.type == SNTRuleTypeCertificate) {
NSCharacterSet *nonHex =
[[NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEF"] invertedSet];
if ([[newRule.identifier uppercaseString] stringByTrimmingCharactersInSet:nonHex].length !=
64) {
[self printErrorUsageAndExit:@"BINARY or CERTIFICATE rules require a valid SHA-256"];
}
}
if (check) {
if (!newRule.identifier) return [self printErrorUsageAndExit:@"--check requires --identifier"];
return [self printStateOfRule:newRule daemonConnection:self.daemonConn];
}
if (newRule.state == SNTRuleStateUnknown) {
[self printErrorUsageAndExit:@"No state specified"];
} else if (!newRule.identifier) {
@@ -210,87 +231,72 @@ REGISTER_COMMAND_NAME(@"rule")
}
- (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)daemonConn {
id<SNTDaemonControlXPC> rop = [daemonConn synchronousRemoteObjectProxy];
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.identifier : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.identifier : nil;
NSString *teamID = (rule.type == SNTRuleTypeTeamID) ? rule.identifier : nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
NSString *signingID = (rule.type == SNTRuleTypeSigningID) ? rule.identifier : nil;
__block NSMutableString *output;
[[daemonConn remoteObjectProxy] decisionForFilePath:nil
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
reply:^(SNTEventState s) {
output = (SNTEventStateAllow & s)
? @"Allowed".mutableCopy
: @"Blocked".mutableCopy;
switch (s) {
case SNTEventStateAllowUnknown:
case SNTEventStateBlockUnknown:
[output appendString:@" (Unknown)"];
break;
case SNTEventStateAllowBinary:
case SNTEventStateBlockBinary:
[output appendString:@" (Binary)"];
break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate:
[output appendString:@" (Certificate)"];
break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope:
[output appendString:@" (Scope)"];
break;
case SNTEventStateAllowCompiler:
[output appendString:@" (Compiler)"];
break;
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID:
[output appendString:@" (TeamID)"];
break;
default: output = @"None".mutableCopy; break;
}
if (isatty(STDOUT_FILENO)) {
if ((SNTEventStateAllow & s)) {
[output insertString:@"\033[32m" atIndex:0];
[output appendString:@"\033[0m"];
} else if ((SNTEventStateBlock & s)) {
[output insertString:@"\033[31m" atIndex:0];
[output appendString:@"\033[0m"];
} else {
[output insertString:@"\033[33m" atIndex:0];
[output appendString:@"\033[0m"];
}
}
dispatch_group_leave(group);
}];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
printf("Cannot communicate with daemon");
exit(1);
}
[rop decisionForFilePath:nil
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
signingID:signingID
reply:^(SNTEventState s) {
output =
(SNTEventStateAllow & s) ? @"Allowed".mutableCopy : @"Blocked".mutableCopy;
switch (s) {
case SNTEventStateAllowUnknown:
case SNTEventStateBlockUnknown: [output appendString:@" (Unknown)"]; break;
case SNTEventStateAllowBinary:
case SNTEventStateBlockBinary: [output appendString:@" (Binary)"]; break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate:
[output appendString:@" (Certificate)"];
break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
case SNTEventStateAllowCompiler:
[output appendString:@" (Compiler)"];
break;
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID: [output appendString:@" (TeamID)"]; break;
case SNTEventStateAllowSigningID:
case SNTEventStateBlockSigningID:
[output appendString:@" (SigningID)"];
break;
default: output = @"None".mutableCopy; break;
}
if (isatty(STDOUT_FILENO)) {
if ((SNTEventStateAllow & s)) {
[output insertString:@"\033[32m" atIndex:0];
[output appendString:@"\033[0m"];
} else if ((SNTEventStateBlock & s)) {
[output insertString:@"\033[31m" atIndex:0];
[output appendString:@"\033[0m"];
} else {
[output insertString:@"\033[33m" atIndex:0];
[output appendString:@"\033[0m"];
}
}
}];
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy]
databaseRuleForBinarySHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
reply:^(SNTRule *r) {
if (r.state == SNTRuleStateAllowTransitive) {
NSDate *date =
[NSDate dateWithTimeIntervalSinceReferenceDate:r.timestamp];
[output
appendString:[NSString stringWithFormat:@"\nlast access date: %@",
[date description]]];
}
dispatch_group_leave(group);
}];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
printf("Cannot communicate with daemon");
exit(1);
}
[rop databaseRuleForBinarySHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
signingID:signingID
reply:^(SNTRule *r) {
if (r.state == SNTRuleStateAllowTransitive) {
NSDate *date =
[NSDate dateWithTimeIntervalSinceReferenceDate:r.timestamp];
[output appendString:[NSString
stringWithFormat:@"\nlast access date: %@",
[date description]]];
}
}];
printf("%s\n", output.UTF8String);
exit(0);

View File

@@ -46,116 +46,100 @@ REGISTER_COMMAND_NAME(@"status")
- (void)runWithArguments:(NSArray *)arguments {
dispatch_group_t group = dispatch_group_create();
id<SNTDaemonControlXPC> rop = [self.daemonConn synchronousRemoteObjectProxy];
// Daemon status
__block NSString *clientMode;
__block uint64_t cpuEvents, ramEvents;
__block double cpuPeak, ramPeak;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
[rop clientMode:^(SNTClientMode cm) {
switch (cm) {
case SNTClientModeMonitor: clientMode = @"Monitor"; break;
case SNTClientModeLockdown: clientMode = @"Lockdown"; break;
default: clientMode = [NSString stringWithFormat:@"Unknown (%ld)", cm]; break;
}
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] watchdogInfo:^(uint64_t wd_cpuEvents, uint64_t wd_ramEvents,
double wd_cpuPeak, double wd_ramPeak) {
[rop watchdogInfo:^(uint64_t wd_cpuEvents, uint64_t wd_ramEvents, double wd_cpuPeak,
double wd_ramPeak) {
cpuEvents = wd_cpuEvents;
cpuPeak = wd_cpuPeak;
ramEvents = wd_ramEvents;
ramPeak = wd_ramPeak;
dispatch_group_leave(group);
}];
BOOL fileLogging = ([[SNTConfigurator configurator] fileChangesRegex] != nil);
NSString *eventLogType = [[[SNTConfigurator configurator] eventLogTypeRaw] lowercaseString];
SNTConfigurator *configurator = [SNTConfigurator configurator];
// Cache status
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] cacheCounts:^(uint64_t rootCache, uint64_t nonRootCache) {
[rop cacheCounts:^(uint64_t rootCache, uint64_t nonRootCache) {
rootCacheCount = rootCache;
nonRootCacheCount = nonRootCache;
dispatch_group_leave(group);
}];
// Database counts
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1, teamIDRuleCount = -1;
__block int64_t compilerRuleCount = -1, transitiveRuleCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy]
databaseRuleCounts:^(int64_t binary, int64_t certificate, int64_t compiler, int64_t transitive,
int64_t teamID) {
binaryRuleCount = binary;
certRuleCount = certificate;
teamIDRuleCount = teamID;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseEventCount:^(int64_t count) {
__block int64_t eventCount = -1;
__block int64_t binaryRuleCount = -1;
__block int64_t certRuleCount = -1;
__block int64_t teamIDRuleCount = -1;
__block int64_t signingIDRuleCount = -1;
__block int64_t compilerRuleCount = -1;
__block int64_t transitiveRuleCount = -1;
[rop databaseRuleCounts:^(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive, int64_t teamID, int64_t signingID) {
binaryRuleCount = binary;
certRuleCount = certificate;
teamIDRuleCount = teamID;
signingIDRuleCount = signingID;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
}];
[rop databaseEventCount:^(int64_t count) {
eventCount = count;
dispatch_group_leave(group);
}];
// Static rule count
__block int64_t staticRuleCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] staticRuleCount:^(int64_t count) {
[rop staticRuleCount:^(int64_t count) {
staticRuleCount = count;
dispatch_group_leave(group);
}];
// Sync status
__block NSDate *fullSyncLastSuccess;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] fullSyncLastSuccess:^(NSDate *date) {
[rop fullSyncLastSuccess:^(NSDate *date) {
fullSyncLastSuccess = date;
dispatch_group_leave(group);
}];
__block NSDate *ruleSyncLastSuccess;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] ruleSyncLastSuccess:^(NSDate *date) {
[rop ruleSyncLastSuccess:^(NSDate *date) {
ruleSyncLastSuccess = date;
dispatch_group_leave(group);
}];
__block BOOL syncCleanReqd = NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] syncCleanRequired:^(BOOL clean) {
[rop syncCleanRequired:^(BOOL clean) {
syncCleanReqd = clean;
dispatch_group_leave(group);
}];
__block BOOL pushNotifications = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] pushNotifications:^(BOOL response) {
[rop pushNotifications:^(BOOL response) {
pushNotifications = response;
dispatch_group_leave(group);
}];
}
__block BOOL enableBundles = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] enableBundles:^(BOOL response) {
[rop enableBundles:^(BOOL response) {
enableBundles = response;
dispatch_group_leave(group);
}];
}
__block BOOL enableTransitiveRules = NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] enableTransitiveRules:^(BOOL response) {
[rop enableTransitiveRules:^(BOOL response) {
enableTransitiveRules = response;
dispatch_group_leave(group);
}];
__block BOOL watchItemsEnabled = NO;
@@ -163,20 +147,16 @@ REGISTER_COMMAND_NAME(@"status")
__block NSString *watchItemsPolicyVersion = nil;
__block NSString *watchItemsConfigPath = nil;
__block NSTimeInterval watchItemsLastUpdateEpoch = 0;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy]
watchItemsState:^(BOOL enabled, uint64_t ruleCount, NSString *policyVersion,
NSString *configPath, NSTimeInterval lastUpdateEpoch) {
watchItemsEnabled = enabled;
if (enabled) {
watchItemsRuleCount = ruleCount;
watchItemsPolicyVersion = policyVersion;
watchItemsConfigPath = configPath;
watchItemsLastUpdateEpoch = lastUpdateEpoch;
}
dispatch_group_leave(group);
}];
[rop watchItemsState:^(BOOL enabled, uint64_t ruleCount, NSString *policyVersion,
NSString *configPath, NSTimeInterval lastUpdateEpoch) {
watchItemsEnabled = enabled;
if (enabled) {
watchItemsRuleCount = ruleCount;
watchItemsPolicyVersion = policyVersion;
watchItemsConfigPath = configPath;
watchItemsLastUpdateEpoch = lastUpdateEpoch;
}
}];
// Wait a maximum of 5s for stats collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
@@ -205,6 +185,7 @@ REGISTER_COMMAND_NAME(@"status")
@"daemon" : @{
@"driver_connected" : @(YES),
@"mode" : clientMode ?: @"null",
@"log_type" : eventLogType,
@"file_logging" : @(fileLogging),
@"watchdog_cpu_events" : @(cpuEvents),
@"watchdog_ram_events" : @(ramEvents),
@@ -218,6 +199,8 @@ REGISTER_COMMAND_NAME(@"status")
@"database" : @{
@"binary_rules" : @(binaryRuleCount),
@"certificate_rules" : @(certRuleCount),
@"teamid_rules" : @(teamIDRuleCount),
@"signingid_rules" : @(signingIDRuleCount),
@"compiler_rules" : @(compilerRuleCount),
@"transitive_rules" : @(transitiveRuleCount),
@"events_pending_upload" : @(eventCount),
@@ -242,7 +225,7 @@ REGISTER_COMMAND_NAME(@"status")
@"enabled" : @(watchItemsEnabled),
@"rule_count" : @(watchItemsRuleCount),
@"policy_version" : watchItemsPolicyVersion,
@"config_path" : watchItemsConfigPath,
@"config_path" : watchItemsConfigPath ?: @"null",
@"last_policy_update" : watchItemsLastUpdateStr ?: @"null",
};
} else {
@@ -265,6 +248,7 @@ REGISTER_COMMAND_NAME(@"status")
} else {
printf(">>> Daemon Info\n");
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-25s | %s\n", "Log Type", [eventLogType UTF8String]);
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
@@ -282,6 +266,7 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %lld\n", "TeamID Rules", teamIDRuleCount);
printf(" %-25s | %lld\n", "SigningID Rules", signingIDRuleCount);
printf(" %-25s | %lld\n", "Compiler Rules", compilerRuleCount);
printf(" %-25s | %lld\n", "Transitive Rules", transitiveRuleCount);
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
@@ -296,7 +281,7 @@ REGISTER_COMMAND_NAME(@"status")
if (watchItemsEnabled) {
printf(" %-25s | %s\n", "Policy Version", watchItemsPolicyVersion.UTF8String);
printf(" %-25s | %llu\n", "Rule Count", watchItemsRuleCount);
printf(" %-25s | %s\n", "Config Path", watchItemsConfigPath.UTF8String);
printf(" %-25s | %s\n", "Config Path", (watchItemsConfigPath ?: @"(embedded)").UTF8String);
printf(" %-25s | %s\n", "Last Policy Update", watchItemsLastUpdateStr.UTF8String);
}

View File

@@ -49,6 +49,7 @@ objc_library(
":WatchItemPolicy",
"//Source/common:PrefixTree",
"//Source/common:SNTLogging",
"//Source/common:String",
"//Source/common:Unit",
],
)
@@ -137,6 +138,9 @@ objc_library(
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTRule",
"//Source/common:SantaCache",
"//Source/common:SantaVnode",
"//Source/common:SantaVnodeHash",
],
)
@@ -229,6 +233,7 @@ objc_library(
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SantaVnode",
"//Source/common:String",
"@MOLCodesignChecker",
],
)
@@ -334,6 +339,7 @@ objc_library(
":EndpointSecurityLogger",
":EndpointSecurityMessage",
":Metrics",
":RateLimiter",
":SNTDecisionCache",
":SNTEndpointSecurityClient",
":SNTEndpointSecurityEventHandler",
@@ -342,6 +348,8 @@ objc_library(
"//Source/common:Platform",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/common:SNTStrengthify",
"//Source/common:SantaCache",
"//Source/common:SantaVnode",
"//Source/common:SantaVnodeHash",
@@ -376,12 +384,24 @@ objc_library(
":EndpointSecurityClient",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SantaCache",
"//Source/common:SantaVnode",
"//Source/common:SantaVnodeHash",
],
)
objc_library(
name = "RateLimiter",
srcs = ["EventProviders/RateLimiter.mm"],
hdrs = ["EventProviders/RateLimiter.h"],
deps = [
":Metrics",
"//Source/common:BranchPrediction",
"//Source/common:SystemResources",
],
)
objc_library(
name = "EndpointSecurityEnricher",
srcs = ["EventProviders/EndpointSecurity/Enricher.mm"],
@@ -409,6 +429,7 @@ objc_library(
":EndpointSecurityEnrichedTypes",
":EndpointSecurityMessage",
":SNTDecisionCache",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
],
@@ -425,6 +446,9 @@ objc_library(
":EndpointSecurityEnrichedTypes",
":EndpointSecurityMessage",
":SNTDecisionCache",
"//Source/common:SantaCache",
"//Source/common:SantaVnode",
"//Source/common:SantaVnodeHash",
],
)
@@ -434,6 +458,7 @@ objc_library(
hdrs = ["Logs/EndpointSecurity/Serializers/Empty.h"],
deps = [
":EndpointSecuritySerializer",
"//Source/common:SNTCachedDecision",
],
)
@@ -446,6 +471,7 @@ objc_library(
],
deps = [
":EndpointSecurityMessage",
"//Source/common:String",
],
)
@@ -460,7 +486,6 @@ objc_library(
":EndpointSecuritySerializerUtilities",
":SNTDecisionCache",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
],
@@ -479,6 +504,7 @@ objc_library(
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
"//Source/common:String",
"//Source/common:santa_cc_proto_library_wrapper",
],
)
@@ -547,6 +573,7 @@ objc_library(
":EndpointSecurityWriterNull",
":EndpointSecurityWriterSpool",
":EndpointSecurityWriterSyslog",
":SNTDecisionCache",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
@@ -672,6 +699,7 @@ objc_library(
":Metrics",
":SNTCompilerController",
":SNTDatabaseController",
":SNTDecisionCache",
":SNTEventTable",
":SNTExecutionController",
":SNTNotificationQueue",
@@ -953,6 +981,16 @@ santa_unit_test(
],
)
santa_unit_test(
name = "RateLimiterTest",
srcs = ["EventProviders/RateLimiterTest.mm"],
deps = [
":Metrics",
":RateLimiter",
"//Source/common:SystemResources",
],
)
santa_unit_test(
name = "EndpointSecuritySerializerEmptyTest",
srcs = ["Logs/EndpointSecurity/Serializers/EmptyTest.mm"],
@@ -1098,6 +1136,7 @@ santa_unit_test(
":MockEndpointSecurityAPI",
":SNTEndpointSecurityClient",
":WatchItemPolicy",
"//Source/common:SNTConfigurator",
"//Source/common:TestUtils",
"@OCMock",
"@com_google_googletest//:gtest",
@@ -1284,6 +1323,7 @@ test_suite(
":EndpointSecurityWriterFileTest",
":EndpointSecurityWriterSpoolTest",
":MetricsTest",
":RateLimiterTest",
":SNTApplicationCoreMetricsTest",
":SNTCompilerControllerTest",
":SNTDecisionCacheTest",

View File

@@ -39,9 +39,9 @@
bail = YES;
return;
}
[db close];
[[NSFileManager defaultManager] removeItemAtPath:[db databasePath] error:NULL];
[db open];
[self closeDeleteReopenDatabase:db];
} else if ([db userVersion] > [self currentSupportedVersion]) {
[self closeDeleteReopenDatabase:db];
}
}];
@@ -58,11 +58,22 @@
return nil;
}
- (void)closeDeleteReopenDatabase:(FMDatabase *)db {
[db close];
[[NSFileManager defaultManager] removeItemAtPath:[db databasePath] error:NULL];
[db open];
}
- (uint32_t)initializeDatabase:(FMDatabase *)db fromVersion:(uint32_t)version {
[self doesNotRecognizeSelector:_cmd];
return 0;
}
- (uint32_t)currentSupportedVersion {
[self doesNotRecognizeSelector:_cmd];
return 0;
}
/// Called at the end of initialization to ensure the table in the
/// database exists and uses the latest schema.
- (void)updateTableSchema {

View File

@@ -18,8 +18,14 @@
#import "Source/common/SNTStoredEvent.h"
static const uint32_t kEventTableCurrentVersion = 3;
@implementation SNTEventTable
- (uint32_t)currentSupportedVersion {
return kEventTableCurrentVersion;
}
- (uint32_t)initializeDatabase:(FMDatabase *)db fromVersion:(uint32_t)version {
int newVersion = 0;

View File

@@ -57,10 +57,16 @@
- (NSUInteger)teamIDRuleCount;
///
/// @return Rule for binary or certificate with given SHA-256. The binary rule will be returned
/// if it exists. If not, the certificate rule will be returned if it exists.
/// @return Number of signing ID rules in the database
///
- (NSUInteger)signingIDRuleCount;
///
/// @return Rule for binary, signingID, certificate or teamID (in that order).
/// The first matching rule found is returned.
///
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
signingID:(NSString *)signingID
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID;

View File

@@ -25,6 +25,8 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
static const uint32_t kRuleTableCurrentVersion = 4;
// TODO(nguyenphillip): this should be configurable.
// How many rules must be in database before we start trying to remove transitive rules.
static const NSUInteger kTransitiveRuleCullingThreshold = 500000;
@@ -173,6 +175,10 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
self.criticalSystemBinaries = bins;
}
- (uint32_t)currentSupportedVersion {
return kRuleTableCurrentVersion;
}
- (uint32_t)initializeDatabase:(FMDatabase *)db fromVersion:(uint32_t)version {
// Lock this database from other processes
[[db executeQuery:@"PRAGMA locking_mode = EXCLUSIVE;"] close];
@@ -272,6 +278,14 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
return count;
}
- (NSUInteger)signingIDRuleCount {
__block NSUInteger count = 0;
[self inDatabase:^(FMDatabase *db) {
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=4"];
}];
return count;
}
- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
return [[SNTRule alloc] initWithIdentifier:[rs stringForColumn:@"identifier"]
state:[rs intForColumn:@"state"]
@@ -281,6 +295,7 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
}
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
signingID:(NSString *)signingID
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID {
__block SNTRule *rule;
@@ -288,12 +303,27 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
// Look for a static rule that matches.
NSDictionary *staticRules = [[SNTConfigurator configurator] staticRules];
if (staticRules.count) {
// IMPORTANT: The order static rules are checked here should be the same
// order as given by the SQL query for the rules database.
rule = staticRules[binarySHA256];
if (rule.type == SNTRuleTypeBinary) return rule;
if (rule.type == SNTRuleTypeBinary) {
return rule;
}
rule = staticRules[signingID];
if (rule.type == SNTRuleTypeSigningID) {
return rule;
}
rule = staticRules[certificateSHA256];
if (rule.type == SNTRuleTypeCertificate) return rule;
if (rule.type == SNTRuleTypeCertificate) {
return rule;
}
rule = staticRules[teamID];
if (rule.type == SNTRuleTypeTeamID) return rule;
if (rule.type == SNTRuleTypeTeamID) {
return rule;
}
}
// Now query the database.
@@ -301,7 +331,7 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
// NOTE: This code is written with the intention that the binary rule is searched for first
// as Santa is designed to go with the most-specific rule possible.
//
// The intended order of precedence is Binaries > Certificates > Team IDs.
// The intended order of precedence is Binaries > Signing IDs > Certificates > Team IDs.
//
// As such the query should have "ORDER BY type DESC" before the LIMIT, to ensure that is the
// case. However, in all tested versions of SQLite that ORDER BY clause is unnecessary: the query
@@ -316,10 +346,12 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
// There is a test for this in SNTRuleTableTests in case SQLite behavior changes in the future.
//
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs =
[db executeQuery:@"SELECT * FROM rules WHERE (identifier=? and type=1) OR "
@"(identifier=? AND type=2) OR (identifier=? AND type=3) LIMIT 1",
binarySHA256, certificateSHA256, teamID];
FMResultSet *rs = [db executeQuery:@"SELECT * FROM rules WHERE "
@" (identifier=? and type=1) "
@"OR (identifier=? AND type=4) "
@"OR (identifier=? AND type=2) "
@"OR (identifier=? AND type=3) LIMIT 1",
binarySHA256, signingID, certificateSHA256, teamID];
if ([rs next]) {
rule = [self ruleFromResultSet:rs];
}

View File

@@ -43,9 +43,22 @@
return r;
}
- (SNTRule *)_exampleSigningIDRuleIsPlatform:(BOOL)isPlatformBinary {
SNTRule *r = [[SNTRule alloc] init];
if (isPlatformBinary) {
r.identifier = @"platform:signingID";
} else {
r.identifier = @"teamID:signingID";
}
r.state = SNTRuleStateBlock;
r.type = SNTRuleTypeSigningID;
r.customMsg = @"A teamID rule";
return r;
}
- (SNTRule *)_exampleBinaryRule {
SNTRule *r = [[SNTRule alloc] init];
r.identifier = @"a";
r.identifier = @"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670";
r.state = SNTRuleStateBlock;
r.type = SNTRuleTypeBinary;
r.customMsg = @"A rule";
@@ -54,7 +67,7 @@
- (SNTRule *)_exampleCertRule {
SNTRule *r = [[SNTRule alloc] init];
r.identifier = @"b";
r.identifier = @"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258";
r.state = SNTRuleStateAllow;
r.type = SNTRuleTypeCertificate;
return r;
@@ -112,7 +125,7 @@
- (void)testAddInvalidRule {
SNTRule *r = [[SNTRule alloc] init];
r.identifier = @"a";
r.identifier = @"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258";
r.type = SNTRuleTypeCertificate;
NSError *error;
@@ -125,12 +138,21 @@
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil];
SNTRule *r = [self.sut
ruleForBinarySHA256:@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670"
signingID:nil
certificateSHA256:nil
teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqualObjects(r.identifier,
@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670");
XCTAssertEqual(r.type, SNTRuleTypeBinary);
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil teamID:nil];
r = [self.sut
ruleForBinarySHA256:@"b6ee1c3c5a715c049d14a8457faa6b6701b8507efe908300e238e0768bd759c2"
signingID:nil
certificateSHA256:nil
teamID:nil];
XCTAssertNil(r);
}
@@ -139,12 +161,21 @@
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b" teamID:nil];
SNTRule *r = [self.sut
ruleForBinarySHA256:nil
signingID:nil
certificateSHA256:@"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258"
teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"b");
XCTAssertEqualObjects(r.identifier,
@"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258");
XCTAssertEqual(r.type, SNTRuleTypeCertificate);
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil];
r = [self.sut
ruleForBinarySHA256:nil
signingID:nil
certificateSHA256:@"5bdab1288fc16892fef50c658db54f1e2e19cf8f71cc55f77de2b95e051e2562"
teamID:nil];
XCTAssertNil(r);
}
@@ -153,38 +184,108 @@
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"teamID"];
SNTRule *r = [self.sut ruleForBinarySHA256:nil
signingID:nil
certificateSHA256:nil
teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"teamID");
XCTAssertEqual(r.type, SNTRuleTypeTeamID);
XCTAssertEqual([self.sut teamIDRuleCount], 1);
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"nonexistentTeamID"];
r = [self.sut ruleForBinarySHA256:nil
signingID:nil
certificateSHA256:nil
teamID:@"nonexistentTeamID"];
XCTAssertNil(r);
}
- (void)testFetchSigningIDRule {
[self.sut addRules:@[
[self _exampleBinaryRule], [self _exampleSigningIDRuleIsPlatform:YES],
[self _exampleSigningIDRuleIsPlatform:NO]
]
cleanSlate:NO
error:nil];
XCTAssertEqual([self.sut signingIDRuleCount], 2);
SNTRule *r = [self.sut ruleForBinarySHA256:nil
signingID:@"teamID:signingID"
certificateSHA256:nil
teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"teamID:signingID");
XCTAssertEqual(r.type, SNTRuleTypeSigningID);
r = [self.sut ruleForBinarySHA256:nil
signingID:@"platform:signingID"
certificateSHA256:nil
teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"platform:signingID");
XCTAssertEqual(r.type, SNTRuleTypeSigningID);
r = [self.sut ruleForBinarySHA256:nil signingID:@"nonexistent" certificateSHA256:nil teamID:nil];
XCTAssertNil(r);
}
- (void)testFetchRuleOrdering {
[self.sut
addRules:@[ [self _exampleCertRule], [self _exampleBinaryRule], [self _exampleTeamIDRule] ]
cleanSlate:NO
error:nil];
[self.sut addRules:@[
[self _exampleCertRule], [self _exampleBinaryRule], [self _exampleTeamIDRule],
[self _exampleSigningIDRuleIsPlatform:NO]
]
cleanSlate:NO
error:nil];
// This test verifies that the implicit rule ordering we've been abusing is still working.
// See the comment in SNTRuleTable#ruleForBinarySHA256:certificateSHA256:teamID
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"b" teamID:@"teamID"];
SNTRule *r = [self.sut
ruleForBinarySHA256:@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670"
signingID:@"teamID:signingID"
certificateSHA256:@"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258"
teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqualObjects(r.identifier,
@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");
r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"unknowncert" teamID:@"teamID"];
r = [self.sut
ruleForBinarySHA256:@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670"
signingID:@"teamID:signingID"
certificateSHA256:@"unknowncert"
teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqualObjects(r.identifier,
@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");
r = [self.sut ruleForBinarySHA256:@"unknown" certificateSHA256:@"b" teamID:@"teamID"];
r = [self.sut
ruleForBinarySHA256:@"unknown"
signingID:@"unknown"
certificateSHA256:@"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258"
teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"b");
XCTAssertEqualObjects(r.identifier,
@"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258");
XCTAssertEqual(r.type, SNTRuleTypeCertificate, @"Implicit rule ordering failed");
r = [self.sut ruleForBinarySHA256:@"unknown"
signingID:@"teamID:signingID"
certificateSHA256:@"unknown"
teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"teamID:signingID");
XCTAssertEqual(r.type, SNTRuleTypeSigningID, @"Implicit rule ordering failed (SigningID)");
r = [self.sut ruleForBinarySHA256:@"unknown"
signingID:@"unknown"
certificateSHA256:@"unknown"
teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"teamID");
XCTAssertEqual(r.type, SNTRuleTypeTeamID, @"Implicit rule ordering failed (TeamID)");
}
- (void)testBadDatabase {

View File

@@ -17,6 +17,7 @@
#include <Kernel/kern/cs_blobs.h>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -32,22 +33,27 @@ static constexpr WatchItemPathType kWatchItemPolicyDefaultPathType =
WatchItemPathType::kLiteral;
static constexpr bool kWatchItemPolicyDefaultAllowReadAccess = false;
static constexpr bool kWatchItemPolicyDefaultAuditOnly = true;
static constexpr bool kWatchItemPolicyDefaultInvertProcessExceptions = false;
struct WatchItemPolicy {
struct Process {
Process(std::string bp, std::string sid, std::string ti,
std::vector<uint8_t> cdh, std::string ch)
std::vector<uint8_t> cdh, std::string ch, std::optional<bool> pb)
: binary_path(bp),
signing_id(sid),
team_id(ti),
cdhash(std::move(cdh)),
certificate_sha256(ch) {}
certificate_sha256(ch),
platform_binary(pb) {}
bool operator==(const Process &other) const {
return binary_path == other.binary_path &&
signing_id == other.signing_id && team_id == other.team_id &&
cdhash == other.cdhash &&
certificate_sha256 == other.certificate_sha256;
certificate_sha256 == other.certificate_sha256 &&
platform_binary.has_value() == other.platform_binary.has_value() &&
platform_binary.value_or(false) ==
other.platform_binary.value_or(false);
}
bool operator!=(const Process &other) const { return !(*this == other); }
@@ -57,25 +63,30 @@ struct WatchItemPolicy {
std::string team_id;
std::vector<uint8_t> cdhash;
std::string certificate_sha256;
std::optional<bool> platform_binary;
};
WatchItemPolicy(std::string_view n, std::string_view p,
WatchItemPathType pt = kWatchItemPolicyDefaultPathType,
bool ara = kWatchItemPolicyDefaultAllowReadAccess,
bool ao = kWatchItemPolicyDefaultAuditOnly,
bool ipe = kWatchItemPolicyDefaultInvertProcessExceptions,
std::vector<Process> procs = {})
: name(n),
path(p),
path_type(pt),
allow_read_access(ara),
audit_only(ao),
invert_process_exceptions(ipe),
processes(std::move(procs)) {}
bool operator==(const WatchItemPolicy &other) const {
return name == other.name && path == other.path &&
path_type == other.path_type &&
allow_read_access == other.allow_read_access &&
audit_only == other.audit_only && processes == other.processes;
audit_only == other.audit_only &&
invert_process_exceptions == other.invert_process_exceptions &&
processes == other.processes;
}
bool operator!=(const WatchItemPolicy &other) const {
@@ -87,6 +98,7 @@ struct WatchItemPolicy {
WatchItemPathType path_type;
bool allow_read_access;
bool audit_only;
bool invert_process_exceptions;
std::vector<Process> processes;
};

View File

@@ -39,12 +39,14 @@ extern NSString *const kWatchItemConfigKeyPathsIsPrefix;
extern NSString *const kWatchItemConfigKeyOptions;
extern NSString *const kWatchItemConfigKeyOptionsAllowReadAccess;
extern NSString *const kWatchItemConfigKeyOptionsAuditOnly;
extern NSString *const kWatchItemConfigKeyOptionsInvertProcessExceptions;
extern NSString *const kWatchItemConfigKeyProcesses;
extern NSString *const kWatchItemConfigKeyProcessesBinaryPath;
extern NSString *const kWatchItemConfigKeyProcessesCertificateSha256;
extern NSString *const kWatchItemConfigKeyProcessesSigningID;
extern NSString *const kWatchItemConfigKeyProcessesTeamID;
extern NSString *const kWatchItemConfigKeyProcessesCDHash;
extern NSString *const kWatchItemConfigKeyProcessesPlatformBinary;
// Forward declarations
namespace santa::santad::data_layer {
@@ -69,9 +71,15 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
// Factory
static std::shared_ptr<WatchItems> Create(NSString *config_path,
uint64_t reapply_config_frequency_secs);
// Factory
static std::shared_ptr<WatchItems> Create(NSDictionary *config,
uint64_t reapply_config_frequency_secs);
WatchItems(NSString *config_path_, dispatch_queue_t q, dispatch_source_t timer_source,
WatchItems(NSString *config_path, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void) = nullptr);
WatchItems(NSDictionary *config, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void) = nullptr);
~WatchItems();
void BeginPeriodicTask();
@@ -79,6 +87,8 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
void RegisterClient(id<SNTEndpointSecurityDynamicEventHandler> client);
void SetConfigPath(NSString *config_path);
void SetConfig(NSDictionary *config);
VersionAndPolicies FindPolciesForPaths(const std::vector<std::string_view> &paths);
std::optional<WatchItemsState> State();
@@ -86,6 +96,9 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
friend class santa::santad::data_layer::WatchItemsPeer;
private:
static std::shared_ptr<WatchItems> CreateInternal(NSString *config_path, NSDictionary *config,
uint64_t reapply_config_frequency_secs);
NSDictionary *ReadConfig();
NSDictionary *ReadConfigLocked() ABSL_SHARED_LOCKS_REQUIRED(lock_);
void ReloadConfig(NSDictionary *new_config);
@@ -97,6 +110,7 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
std::set<std::pair<std::string, WatchItemPathType>> &paths);
NSString *config_path_;
NSDictionary *embedded_config_;
dispatch_queue_t q_;
dispatch_source_t timer_source_;
void (^periodic_task_complete_f_)(void);

View File

@@ -18,7 +18,6 @@
#include <Kernel/kern/cs_blobs.h>
#include <ctype.h>
#include <glob.h>
#include <objc/NSObjCRuntime.h>
#include <sys/syslimits.h>
#include <algorithm>
@@ -35,8 +34,12 @@
#import "Source/common/PrefixTree.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/String.h"
#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;
@@ -50,12 +53,14 @@ NSString *const kWatchItemConfigKeyPathsIsPrefix = @"IsPrefix";
NSString *const kWatchItemConfigKeyOptions = @"Options";
NSString *const kWatchItemConfigKeyOptionsAllowReadAccess = @"AllowReadAccess";
NSString *const kWatchItemConfigKeyOptionsAuditOnly = @"AuditOnly";
NSString *const kWatchItemConfigKeyOptionsInvertProcessExceptions = @"InvertProcessExceptions";
NSString *const kWatchItemConfigKeyProcesses = @"Processes";
NSString *const kWatchItemConfigKeyProcessesBinaryPath = @"BinaryPath";
NSString *const kWatchItemConfigKeyProcessesCertificateSha256 = @"CertificateSha256";
NSString *const kWatchItemConfigKeyProcessesSigningID = @"SigningID";
NSString *const kWatchItemConfigKeyProcessesTeamID = @"TeamID";
NSString *const kWatchItemConfigKeyProcessesCDHash = @"CDHash";
NSString *const kWatchItemConfigKeyProcessesPlatformBinary = @"PlatformBinary";
// https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/
static constexpr NSUInteger kMaxTeamIDLength = 10;
@@ -255,7 +260,7 @@ std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSEr
return Unit{};
}
path_list.push_back({std::string(path_str.UTF8String, path_str.length), path_type});
path_list.push_back({NSStringToUTF8String(path_str), path_type});
} else if ([path isKindOfClass:[NSString class]]) {
if (!LenRangeValidator(1, PATH_MAX)(path, err)) {
PopulateError(err, [NSString stringWithFormat:@"Invalid path length: %@",
@@ -264,8 +269,8 @@ std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSEr
return Unit{};
}
path_list.push_back({std::string(((NSString *)path).UTF8String, ((NSString *)path).length),
kWatchItemPolicyDefaultPathType});
path_list.push_back(
{NSStringToUTF8String(((NSString *)path)), kWatchItemPolicyDefaultPathType});
} else {
PopulateError(
err, [NSString stringWithFormat:
@@ -292,6 +297,8 @@ std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSEr
/// <string>AAAA</string>
/// <key>TeamID</key>
/// <string>BBBB</string>
/// <key>PlatformBinary</key>
/// <true/>
/// </dict>
/// <dict>
/// <key>CertificateSha256</key>
@@ -319,7 +326,9 @@ std::variant<Unit, ProcessList> VerifyConfigWatchItemProcesses(NSDictionary *wat
false, HexValidator(CS_CDHASH_LEN * 2)) ||
!VerifyConfigKey(process, kWatchItemConfigKeyProcessesCertificateSha256,
[NSString class], err, false,
HexValidator(CC_SHA256_DIGEST_LENGTH * 2))) {
HexValidator(CC_SHA256_DIGEST_LENGTH * 2)) ||
!VerifyConfigKey(process, kWatchItemConfigKeyProcessesPlatformBinary,
[NSNumber class], err, false, nil)) {
PopulateError(err, @"Failed to verify key content");
return false;
}
@@ -329,18 +338,22 @@ std::variant<Unit, ProcessList> VerifyConfigWatchItemProcesses(NSDictionary *wat
!process[kWatchItemConfigKeyProcessesSigningID] &&
!process[kWatchItemConfigKeyProcessesTeamID] &&
!process[kWatchItemConfigKeyProcessesCDHash] &&
!process[kWatchItemConfigKeyProcessesCertificateSha256]) {
!process[kWatchItemConfigKeyProcessesCertificateSha256] &&
!process[kWatchItemConfigKeyProcessesPlatformBinary]) {
PopulateError(err, @"No valid attributes set in process dictionary");
return false;
}
proc_list.push_back(WatchItemPolicy::Process(
std::string([(process[kWatchItemConfigKeyProcessesBinaryPath] ?: @"") UTF8String]),
std::string([(process[kWatchItemConfigKeyProcessesSigningID] ?: @"") UTF8String]),
std::string([(process[kWatchItemConfigKeyProcessesTeamID] ?: @"") UTF8String]),
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesBinaryPath] ?: @""),
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesSigningID] ?: @""),
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesTeamID] ?: @""),
HexStringToBytes(process[kWatchItemConfigKeyProcessesCDHash]),
std::string(
[(process[kWatchItemConfigKeyProcessesCertificateSha256] ?: @"") UTF8String])));
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesCertificateSha256] ?: @""),
process[kWatchItemConfigKeyProcessesPlatformBinary]
? std::make_optional(
(bool)[process[kWatchItemConfigKeyProcessesPlatformBinary] boolValue])
: std::nullopt));
return true;
})) {
@@ -364,6 +377,8 @@ std::variant<Unit, ProcessList> VerifyConfigWatchItemProcesses(NSDictionary *wat
/// <false/>
/// <key>AuditOnly</key>
/// <false/>
/// <key>InvertProcessExceptions</key>
/// <false/>
/// </dict>
/// <key>Processes</key>
/// <array>
@@ -398,6 +413,11 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
if (!VerifyConfigKey(options, kWatchItemConfigKeyOptionsAuditOnly, [NSNumber class], err)) {
return false;
}
if (!VerifyConfigKey(options, kWatchItemConfigKeyOptionsInvertProcessExceptions,
[NSNumber class], err)) {
return false;
}
}
bool allow_read_access = options[kWatchItemConfigKeyOptionsAllowReadAccess]
@@ -406,6 +426,10 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
bool audit_only = options[kWatchItemConfigKeyOptionsAuditOnly]
? [options[kWatchItemConfigKeyOptionsAuditOnly] boolValue]
: kWatchItemPolicyDefaultAuditOnly;
bool invert_process_exceptions =
options[kWatchItemConfigKeyOptionsInvertProcessExceptions]
? [options[kWatchItemConfigKeyOptionsInvertProcessExceptions] boolValue]
: kWatchItemPolicyDefaultInvertProcessExceptions;
std::variant<Unit, ProcessList> proc_list = VerifyConfigWatchItemProcesses(watch_item, err);
if (std::holds_alternative<Unit>(proc_list)) {
@@ -414,8 +438,36 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
for (const PathAndTypePair &path_type_pair : std::get<PathList>(path_list)) {
policies.push_back(std::make_shared<WatchItemPolicy>(
[name UTF8String], path_type_pair.first, path_type_pair.second, allow_read_access, audit_only,
std::get<ProcessList>(proc_list)));
NSStringToUTF8StringView(name), path_type_pair.first, path_type_pair.second,
allow_read_access, audit_only, invert_process_exceptions, std::get<ProcessList>(proc_list)));
}
return true;
}
bool IsWatchItemNameValid(NSString *watch_item_name, NSError **err) {
if (!watch_item_name) {
// This shouldn't be possible as written, but handle just in case
PopulateError(err, [NSString stringWithFormat:@"nil watch item name"]);
return false;
}
static dispatch_once_t once_token;
static NSRegularExpression *regex;
dispatch_once(&once_token, ^{
// Should only match legal C identifiers
regex = [NSRegularExpression regularExpressionWithPattern:@"^[A-Za-z_][A-Za-z0-9_]*$"
options:0
error:nil];
});
if ([regex numberOfMatchesInString:watch_item_name
options:0
range:NSMakeRange(0, watch_item_name.length)] != 1) {
PopulateError(err, [NSString stringWithFormat:@"Key name must match regular expression \"%@\"",
regex.pattern]);
return false;
}
return true;
@@ -454,9 +506,11 @@ bool ParseConfig(NSDictionary *config, std::vector<std::shared_ptr<WatchItemPoli
return false;
}
if ([(NSString *)key length] == 0) {
PopulateError(err, [NSString stringWithFormat:@"Invalid %@ key with length zero",
kWatchItemConfigKeyWatchItems]);
if (!IsWatchItemNameValid((NSString *)key, err)) {
PopulateError(
err, [NSString
stringWithFormat:@"Invalid %@ key '%@': %@", kWatchItemConfigKeyWatchItems, key,
(err && *err) ? (*err).localizedDescription : @"Unknown failure"]);
return false;
}
@@ -481,24 +535,53 @@ bool ParseConfig(NSDictionary *config, std::vector<std::shared_ptr<WatchItemPoli
std::shared_ptr<WatchItems> WatchItems::Create(NSString *config_path,
uint64_t reapply_config_frequency_secs) {
return CreateInternal(config_path, nil, reapply_config_frequency_secs);
}
std::shared_ptr<WatchItems> WatchItems::Create(NSDictionary *config,
uint64_t reapply_config_frequency_secs) {
return CreateInternal(nil, config, reapply_config_frequency_secs);
}
std::shared_ptr<WatchItems> WatchItems::CreateInternal(NSString *config_path, NSDictionary *config,
uint64_t reapply_config_frequency_secs) {
if (reapply_config_frequency_secs < kMinReapplyConfigFrequencySecs) {
LOGW(@"Invalid watch item update interval provided: %llu. Min allowed: %llu",
reapply_config_frequency_secs, kMinReapplyConfigFrequencySecs);
return nullptr;
}
if (config_path && config) {
LOGW(@"Invalid arguments creating WatchItems - both config and config_path cannot be set.");
return nullptr;
}
dispatch_queue_t q = dispatch_queue_create("com.google.santa.daemon.watch_items.q",
DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
dispatch_source_t timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q);
dispatch_source_set_timer(timer_source, dispatch_time(DISPATCH_TIME_NOW, 0),
NSEC_PER_SEC * reapply_config_frequency_secs, 0);
return std::make_shared<WatchItems>(config_path, q, timer_source);
if (config_path) {
return std::make_shared<WatchItems>(config_path, q, timer_source);
} else {
return std::make_shared<WatchItems>(config, q, timer_source);
}
}
WatchItems::WatchItems(NSString *config_path, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void))
: config_path_(config_path),
embedded_config_(nil),
q_(q),
timer_source_(timer_source),
periodic_task_complete_f_(periodic_task_complete_f),
watch_items_(std::make_unique<WatchItemsTree>()) {}
WatchItems::WatchItems(NSDictionary *config, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void))
: config_path_(nil),
embedded_config_(config),
q_(q),
timer_source_(timer_source),
periodic_task_complete_f_(periodic_task_complete_f),
@@ -577,13 +660,15 @@ void WatchItems::UpdateCurrentState(
std::swap(currently_monitored_paths_, new_monitored_paths);
current_config_ = new_config;
if (new_config) {
policy_version_ = [new_config[kWatchItemConfigKeyVersion] UTF8String];
policy_version_ = NSStringToUTF8String(new_config[kWatchItemConfigKeyVersion]);
} else {
policy_version_ = "";
}
last_update_time_ = [[NSDate date] timeIntervalSince1970];
LOGD(@"Changes to watch items detected, notifying registered clients.");
for (const id<SNTEndpointSecurityDynamicEventHandler> &client : registerd_clients_) {
// Note: Enable clients on an async queue in case they perform any
// synchronous work that could trigger ES events. Otherwise they might
@@ -647,7 +732,7 @@ void WatchItems::BeginPeriodicTask() {
return;
}
shared_watcher->ReloadConfig(shared_watcher->ReadConfig());
shared_watcher->ReloadConfig(embedded_config_ ?: shared_watcher->ReadConfig());
if (shared_watcher->periodic_task_complete_f_) {
shared_watcher->periodic_task_complete_f_();
@@ -677,11 +762,21 @@ void WatchItems::SetConfigPath(NSString *config_path) {
{
absl::MutexLock lock(&lock_);
config_path_ = config_path;
embedded_config_ = nil;
config = ReadConfigLocked();
}
ReloadConfig(config);
}
void WatchItems::SetConfig(NSDictionary *config) {
{
absl::MutexLock lock(&lock_);
config_path_ = nil;
embedded_config_ = config;
}
ReloadConfig(embedded_config_);
}
std::optional<WatchItemsState> WatchItems::State() {
absl::ReaderMutexLock lock(&lock_);

View File

@@ -35,6 +35,7 @@
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;
@@ -51,6 +52,7 @@ namespace santa::santad::data_layer {
extern bool ParseConfig(NSDictionary *config,
std::vector<std::shared_ptr<WatchItemPolicy>> &policies, NSError **err);
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);
@@ -60,12 +62,19 @@ extern std::variant<Unit, santatest::ProcessList> VerifyConfigWatchItemProcesses
NSDictionary *watch_item, NSError **err);
class WatchItemsPeer : public WatchItems {
public:
using WatchItems::ReloadConfig;
using WatchItems::WatchItems;
using WatchItems::ReloadConfig;
using WatchItems::SetConfig;
using WatchItems::SetConfigPath;
using WatchItems::config_path_;
using WatchItems::embedded_config_;
};
} // namespace santa::santad::data_layer
using santa::santad::data_layer::IsWatchItemNameValid;
using santa::santad::data_layer::ParseConfig;
using santa::santad::data_layer::ParseConfigSingleWatchItem;
using santa::santad::data_layer::VerifyConfigWatchItemPaths;
@@ -189,7 +198,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
// Changes in config dictionary will update policy info even if the
// filesystem didn't change.
{
WatchItemsPeer watchItems(nil, NULL, NULL);
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
[self pushd:@"a"];
watchItems.ReloadConfig(configAllFilesOriginal);
@@ -210,7 +219,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
// Changes to fileystem structure are reflected when a config is reloaded
{
WatchItemsPeer watchItems(nil, NULL, NULL);
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
[self pushd:@"a"];
watchItems.ReloadConfig(configAllFilesOriginal);
[self popd];
@@ -311,7 +320,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
}
});
WatchItemsPeer watchItems(nil, NULL, NULL);
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
WatchItems::VersionAndPolicies policies;
// Resultant vector is same size as input vector
@@ -514,7 +523,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
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],
WatchItemPolicy::Process("mypath", "", "", {}, ""));
WatchItemPolicy::Process("mypath", "", "", {}, "", std::nullopt));
// Test SigningID length limits
proc_list = VerifyConfigWatchItemProcesses(@{
@@ -533,7 +542,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
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],
WatchItemPolicy::Process("", "com.google.test", "", {}, ""));
WatchItemPolicy::Process("", "com.google.test", "", {}, "", std::nullopt));
// Test TeamID length limits
proc_list = VerifyConfigWatchItemProcesses(@{
@@ -550,7 +559,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
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],
WatchItemPolicy::Process("", "", "myvalidtid", {}, ""));
WatchItemPolicy::Process("", "", "myvalidtid", {}, "", std::nullopt));
// Test CDHash length limits
proc_list = VerifyConfigWatchItemProcesses(@{
@@ -577,7 +586,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
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],
WatchItemPolicy::Process("", "", "", cdhashBytes, ""));
WatchItemPolicy::Process("", "", "", cdhashBytes, "", std::nullopt));
// Test Cert Hash length limits
proc_list = VerifyConfigWatchItemProcesses(@{
@@ -608,7 +617,22 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
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],
WatchItemPolicy::Process("", "", "", {}, [certHash UTF8String]));
WatchItemPolicy::Process("", "", "", {}, [certHash UTF8String], std::nullopt));
// Test valid invalid PlatformBinary type
proc_list = VerifyConfigWatchItemProcesses(
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesPlatformBinary : @"YES"} ]},
&err);
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
// Test valid valid PlatformBinary
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],
WatchItemPolicy::Process("", "", "", {}, "", std::make_optional(true)));
// Test valid multiple attributes, multiple procs
proc_list = VerifyConfigWatchItemProcesses(@{
@@ -619,6 +643,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
kWatchItemConfigKeyProcessesTeamID : @"validtid_1",
kWatchItemConfigKeyProcessesCDHash : cdhash,
kWatchItemConfigKeyProcessesCertificateSha256 : certHash,
kWatchItemConfigKeyProcessesPlatformBinary : @(YES),
},
@{
kWatchItemConfigKeyProcessesBinaryPath : @"mypath2",
@@ -626,6 +651,7 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
kWatchItemConfigKeyProcessesTeamID : @"validtid_2",
kWatchItemConfigKeyProcessesCDHash : cdhash,
kWatchItemConfigKeyProcessesCertificateSha256 : certHash,
kWatchItemConfigKeyProcessesPlatformBinary : @(NO),
},
]
},
@@ -634,10 +660,29 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 2);
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
WatchItemPolicy::Process("mypath1", "com.google.test1", "validtid_1", cdhashBytes,
[certHash UTF8String]));
[certHash UTF8String], std::make_optional(true)));
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[1],
WatchItemPolicy::Process("mypath2", "com.google.test2", "validtid_2", cdhashBytes,
[certHash UTF8String]));
[certHash UTF8String], std::make_optional(false)));
}
- (void)testIsWatchItemNameValid {
// Only legal C identifiers should be accepted
XCTAssertFalse(IsWatchItemNameValid(nil, nil));
XCTAssertFalse(IsWatchItemNameValid(@"", nil));
XCTAssertFalse(IsWatchItemNameValid(@"1abc", nil));
XCTAssertFalse(IsWatchItemNameValid(@"abc-1234", nil));
XCTAssertFalse(IsWatchItemNameValid(@"a=b", nil));
XCTAssertFalse(IsWatchItemNameValid(@"a!b", nil));
XCTAssertTrue(IsWatchItemNameValid(@"_", nil));
XCTAssertTrue(IsWatchItemNameValid(@"_1", nil));
XCTAssertTrue(IsWatchItemNameValid(@"_1_", nil));
XCTAssertTrue(IsWatchItemNameValid(@"abc", nil));
XCTAssertTrue(IsWatchItemNameValid(@"A", nil));
XCTAssertTrue(IsWatchItemNameValid(@"A_B", nil));
XCTAssertTrue(IsWatchItemNameValid(@"FooName", nil));
XCTAssertTrue(IsWatchItemNameValid(@"bar_Name", nil));
}
- (void)testParseConfig {
@@ -670,16 +715,16 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"" : @{}}},
policies, &err));
XCTAssertFalse(
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"1" : @[]}},
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"a" : @[]}},
policies, &err));
XCTAssertFalse(
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"1" : @{}}},
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"a" : @{}}},
policies, &err));
// Minimally successful config with watch item
XCTAssertTrue(ParseConfig(@{
kWatchItemConfigKeyVersion : @"1",
kWatchItemConfigKeyWatchItems : @{@"1" : @{kWatchItemConfigKeyPaths : @[ @"asdf" ]}}
kWatchItemConfigKeyWatchItems : @{@"a" : @{kWatchItemConfigKeyPaths : @[ @"asdf" ]}}
},
policies, &err));
}
@@ -733,6 +778,17 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
},
policies, &err));
XCTAssertFalse(ParseConfigSingleWatchItem(@"", @{
kWatchItemConfigKeyPaths : @[ @"a" ],
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsInvertProcessExceptions : @""}
},
policies, &err));
XCTAssertTrue(ParseConfigSingleWatchItem(@"", @{
kWatchItemConfigKeyPaths : @[ @"a" ],
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsInvertProcessExceptions : @(0)}
},
policies, &err));
// If processes are specified, they must be valid format
// Note: Full tests in `testVerifyConfigWatchItemProcesses`
XCTAssertFalse(ParseConfigSingleWatchItem(
@@ -746,15 +802,17 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
XCTAssertTrue(
ParseConfigSingleWatchItem(@"rule", @{kWatchItemConfigKeyPaths : @[ @"a" ]}, policies, &err));
XCTAssertEqual(policies.size(), 1);
XCTAssertEqual(*policies[0].get(), WatchItemPolicy("rule", "a", kWatchItemPolicyDefaultPathType,
kWatchItemPolicyDefaultAllowReadAccess,
kWatchItemPolicyDefaultAuditOnly, {}));
XCTAssertEqual(
*policies[0].get(),
WatchItemPolicy("rule", "a", kWatchItemPolicyDefaultPathType,
kWatchItemPolicyDefaultAllowReadAccess, kWatchItemPolicyDefaultAuditOnly,
kWatchItemPolicyDefaultInvertProcessExceptions, {}));
// Test multiple paths, options, and processes
policies.clear();
std::vector<WatchItemPolicy::Process> procs = {
WatchItemPolicy::Process("pa", "", "", {}, ""),
WatchItemPolicy::Process("pb", "", "", {}, ""),
WatchItemPolicy::Process("pa", "", "", {}, "", std::nullopt),
WatchItemPolicy::Process("pb", "", "", {}, "", std::nullopt),
};
XCTAssertTrue(ParseConfigSingleWatchItem(@"rule", @{
@@ -762,7 +820,8 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
@[ @"a", @{kWatchItemConfigKeyPathsPath : @"b", kWatchItemConfigKeyPathsIsPrefix : @(YES)} ],
kWatchItemConfigKeyOptions : @{
kWatchItemConfigKeyOptionsAllowReadAccess : @(YES),
kWatchItemConfigKeyOptionsAuditOnly : @(NO)
kWatchItemConfigKeyOptionsAuditOnly : @(NO),
kWatchItemConfigKeyOptionsInvertProcessExceptions : @(YES),
},
kWatchItemConfigKeyProcesses : @[
@{kWatchItemConfigKeyProcessesBinaryPath : @"pa"},
@@ -771,10 +830,10 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
},
policies, &err));
XCTAssertEqual(policies.size(), 2);
XCTAssertEqual(*policies[0].get(),
WatchItemPolicy("rule", "a", kWatchItemPolicyDefaultPathType, true, false, procs));
XCTAssertEqual(*policies[1].get(),
WatchItemPolicy("rule", "b", WatchItemPathType::kPrefix, true, false, procs));
XCTAssertEqual(*policies[0].get(), WatchItemPolicy("rule", "a", kWatchItemPolicyDefaultPathType,
true, false, true, procs));
XCTAssertEqual(*policies[1].get(), WatchItemPolicy("rule", "b", WatchItemPathType::kPrefix, true,
false, true, procs));
}
- (void)testState {
@@ -804,4 +863,23 @@ static NSString *RepeatedString(NSString *str, NSUInteger len) {
XCTAssertGreaterThanOrEqual(state.last_config_load_epoch, startTime);
}
- (void)testSetConfigAndSetConfigPath {
// Test internal state when switching back and forth between path-based and
// dictionary-based config options.
WatchItemsPeer watchItems(@{}, NULL, NULL);
XCTAssertNil(watchItems.config_path_);
XCTAssertNotNil(watchItems.embedded_config_);
watchItems.SetConfigPath(@"/path/to/a/nonexistent/file/so/nothing/is/opened");
XCTAssertNotNil(watchItems.config_path_);
XCTAssertNil(watchItems.embedded_config_);
watchItems.SetConfig(@{});
XCTAssertNil(watchItems.config_path_);
XCTAssertNotNil(watchItems.embedded_config_);
}
@end

View File

@@ -22,6 +22,7 @@
#include <memory>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTMetricSet.h"
#include "Source/common/SantaCache.h"
#import "Source/common/SantaVnode.h"
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
@@ -33,16 +34,29 @@ enum class FlushCacheMode {
kAllCaches,
};
enum class FlushCacheReason {
kClientModeChanged,
kPathRegexChanged,
kRulesChanged,
kStaticRulesChanged,
kExplicitCommand,
kFilesystemUnmounted,
};
class AuthResultCache {
public:
// Santa currently only flushes caches when new DENY rules are added, not
// ALLOW rules. This means this value should be low enough so that if a
// ALLOW rules. This means cache_deny_time_ms should be low enough so that if a
// 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);
AuthResultCache(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
uint64_t cache_deny_time_ms = 1500);
SNTMetricCounter *flush_count, uint64_t cache_deny_time_ms = 1500);
virtual ~AuthResultCache();
AuthResultCache(AuthResultCache &&other) = delete;
@@ -55,7 +69,7 @@ class AuthResultCache {
virtual SNTAction CheckCache(const es_file_t *es_file);
virtual SNTAction CheckCache(SantaVnode vnode_id);
virtual void FlushCache(FlushCacheMode mode);
virtual void FlushCache(FlushCacheMode mode, FlushCacheReason reason);
virtual NSArray<NSNumber *> *CacheCounts();
@@ -66,6 +80,7 @@ class AuthResultCache {
SantaCache<SantaVnode, uint64_t> *nonroot_cache_;
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi_;
SNTMetricCounter *flush_count_;
uint64_t root_devno_;
uint64_t cache_deny_time_ns_;
dispatch_queue_t q_;

View File

@@ -25,6 +25,13 @@
using santa::santad::event_providers::endpoint_security::Client;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
static NSString *const kFlushCacheReasonClientModeChanged = @"ClientModeChanged";
static NSString *const kFlushCacheReasonPathRegexChanged = @"PathRegexChanged";
static NSString *const kFlushCacheReasonRulesChanged = @"RulesChanged";
static NSString *const kFlushCacheReasonStaticRulesChanged = @"StaticRulesChanged";
static NSString *const kFlushCacheReasonExplicitCommand = @"ExplicitCommand";
static NSString *const kFlushCacheReasonFilesystemUnmounted = @"FilesystemUnmounted";
namespace santa::santad::event_providers {
static inline uint64_t GetCurrentUptime() {
@@ -44,9 +51,36 @@ static inline uint64_t TimestampFromCachedValue(uint64_t cachedValue) {
return (cachedValue & ~(0xFF00000000000000));
}
NSString *const FlushCacheReasonToString(FlushCacheReason reason) {
switch (reason) {
case FlushCacheReason::kClientModeChanged: return kFlushCacheReasonClientModeChanged;
case FlushCacheReason::kPathRegexChanged: return kFlushCacheReasonPathRegexChanged;
case FlushCacheReason::kRulesChanged: return kFlushCacheReasonRulesChanged;
case FlushCacheReason::kStaticRulesChanged: return kFlushCacheReasonStaticRulesChanged;
case FlushCacheReason::kExplicitCommand: return kFlushCacheReasonExplicitCommand;
case FlushCacheReason::kFilesystemUnmounted: return kFlushCacheReasonFilesystemUnmounted;
default:
[NSException raise:@"Invalid reason" format:@"Unknown reason value: %d", reason];
return nil;
}
}
std::unique_ptr<AuthResultCache> AuthResultCache::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
SNTMetricSet *metric_set,
uint64_t cache_deny_time_ms) {
SNTMetricCounter *flush_count =
[metric_set counterWithName:@"/santa/flush_count"
fieldNames:@[ @"Reason" ]
helpText:@"Count of times the auth result cache is flushed by reason"];
return std::make_unique<AuthResultCache>(esapi, flush_count, cache_deny_time_ms);
}
AuthResultCache::AuthResultCache(std::shared_ptr<EndpointSecurityAPI> esapi,
uint64_t cache_deny_time_ms)
: esapi_(esapi), cache_deny_time_ns_(cache_deny_time_ms * NSEC_PER_MSEC) {
SNTMetricCounter *flush_count, uint64_t cache_deny_time_ms)
: esapi_(esapi),
flush_count_(flush_count),
cache_deny_time_ns_(cache_deny_time_ms * NSEC_PER_MSEC) {
root_cache_ = new SantaCache<SantaVnode, uint64_t>();
nonroot_cache_ = new SantaCache<SantaVnode, uint64_t>();
@@ -118,7 +152,7 @@ SantaCache<SantaVnode, uint64_t> *AuthResultCache::CacheForVnodeID(SantaVnode vn
return (vnode_id.fsid == root_devno_ || root_devno_ == 0) ? root_cache_ : nonroot_cache_;
}
void AuthResultCache::FlushCache(FlushCacheMode mode) {
void AuthResultCache::FlushCache(FlushCacheMode mode, FlushCacheReason reason) {
nonroot_cache_->clear();
if (mode == FlushCacheMode::kAllCaches) {
root_cache_->clear();
@@ -134,6 +168,8 @@ void AuthResultCache::FlushCache(FlushCacheMode mode) {
shared_esapi->ClearCache(Client());
});
}
[flush_count_ incrementForFieldValues:@[ FlushCacheReasonToString(reason) ]];
}
NSArray<NSNumber *> *AuthResultCache::CacheCounts() {

View File

@@ -29,6 +29,13 @@
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
namespace santa::santad::event_providers {
extern NSString *const FlushCacheReasonToString(FlushCacheReason reason);
}
using santa::santad::event_providers::FlushCacheReasonToString;
// Grab the st_dev number of the root volume to match the root cache
static uint64_t RootDevno() {
@@ -66,14 +73,14 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
- (void)testEmptyCacheExpectedNumberOfCacheCounts {
auto esapi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(esapi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(esapi, nil);
AssertCacheCounts(cache, 0, 0);
}
- (void)testBasicOperation {
auto esapi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(esapi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(esapi, nil);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
es_file_t nonrootFile = MakeCacheableFile(RootDevno() + 123, 222);
@@ -110,7 +117,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
- (void)testFlushCache {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(mockESApi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(mockESApi, nil);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
es_file_t nonrootFile = MakeCacheableFile(RootDevno() + 123, 111);
@@ -121,7 +128,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
AssertCacheCounts(cache, 1, 1);
// Flush non-root only
cache->FlushCache(FlushCacheMode::kNonRootOnly);
cache->FlushCache(FlushCacheMode::kNonRootOnly, FlushCacheReason::kClientModeChanged);
AssertCacheCounts(cache, 1, 0);
@@ -138,7 +145,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
dispatch_semaphore_signal(sema);
return true;
}));
cache->FlushCache(FlushCacheMode::kAllCaches);
cache->FlushCache(FlushCacheMode::kAllCaches, FlushCacheReason::kClientModeChanged);
XCTAssertEqual(0,
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)),
@@ -151,7 +158,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
- (void)testCacheStateMachine {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(mockESApi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(mockESApi, nil);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
@@ -193,7 +200,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
// Create a cache with a lowered cache expiry value
uint64_t expiryMS = 250;
auto cache = std::make_shared<AuthResultCache>(mockESApi, expiryMS);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(mockESApi, nil, expiryMS);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
@@ -215,4 +222,21 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
AssertCacheCounts(cache, 0, 0);
}
- (void)testFlushCacheReasonToString {
std::map<FlushCacheReason, NSString *> reasonToString = {
{FlushCacheReason::kClientModeChanged, @"ClientModeChanged"},
{FlushCacheReason::kPathRegexChanged, @"PathRegexChanged"},
{FlushCacheReason::kRulesChanged, @"RulesChanged"},
{FlushCacheReason::kStaticRulesChanged, @"StaticRulesChanged"},
{FlushCacheReason::kExplicitCommand, @"ExplicitCommand"},
{FlushCacheReason::kFilesystemUnmounted, @"FilesystemUnmounted"},
};
for (const auto &kv : reasonToString) {
XCTAssertEqualObjects(FlushCacheReasonToString(kv.first), kv.second);
}
XCTAssertThrows(FlushCacheReasonToString((FlushCacheReason)12345));
}
@end

View File

@@ -46,7 +46,11 @@ class EnrichedFile {
group_(std::move(other.group_)),
hash_(std::move(other.hash_)) {}
// Note: Move assignment could be safely implemented but not currently needed
EnrichedFile &operator=(EnrichedFile &&other) = delete;
EnrichedFile(const EnrichedFile &other) = delete;
EnrichedFile &operator=(const EnrichedFile &other) = delete;
const std::optional<std::shared_ptr<std::string>> &user() const {
return user_;
@@ -87,7 +91,11 @@ class EnrichedProcess {
real_group_(std::move(other.real_group_)),
executable_(std::move(other.executable_)) {}
// Note: Move assignment could be safely implemented but not currently needed
EnrichedProcess &operator=(EnrichedProcess &&other) = delete;
EnrichedProcess(const EnrichedProcess &other) = delete;
EnrichedProcess &operator=(const EnrichedProcess &other) = delete;
const std::optional<std::shared_ptr<std::string>> &effective_user() const {
return effective_user_;
@@ -123,7 +131,12 @@ class EnrichedEventType {
instigator_(std::move(other.instigator_)),
enrichment_time_(std::move(other.enrichment_time_)) {}
// Note: Move assignment could be safely implemented but not currently needed
// so no sense in implementing across all child classes
EnrichedEventType &operator=(EnrichedEventType &&other) = delete;
EnrichedEventType(const EnrichedEventType &other) = delete;
EnrichedEventType &operator=(const EnrichedEventType &other) = delete;
virtual ~EnrichedEventType() = default;

View File

@@ -34,7 +34,7 @@ class Enricher {
public:
Enricher();
virtual ~Enricher() = default;
virtual std::shared_ptr<EnrichedMessage> Enrich(Message &&msg);
virtual std::unique_ptr<EnrichedMessage> Enrich(Message &&msg);
virtual EnrichedProcess Enrich(
const es_process_t &es_proc,
EnrichOptions options = EnrichOptions::kDefault);

View File

@@ -30,19 +30,19 @@ namespace santa::santad::event_providers::endpoint_security {
Enricher::Enricher() : username_cache_(256), groupname_cache_(256) {}
std::shared_ptr<EnrichedMessage> Enricher::Enrich(Message &&es_msg) {
std::unique_ptr<EnrichedMessage> Enricher::Enrich(Message &&es_msg) {
// TODO(mlw): Consider potential design patterns that could help reduce memory usage under load
// (such as maybe the flyweight pattern)
switch (es_msg->event_type) {
case ES_EVENT_TYPE_NOTIFY_CLOSE:
return std::make_shared<EnrichedMessage>(EnrichedClose(
return std::make_unique<EnrichedMessage>(EnrichedClose(
std::move(es_msg), Enrich(*es_msg->process), Enrich(*es_msg->event.close.target)));
case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA:
return std::make_shared<EnrichedMessage>(EnrichedExchange(
return std::make_unique<EnrichedMessage>(EnrichedExchange(
std::move(es_msg), Enrich(*es_msg->process), Enrich(*es_msg->event.exchangedata.file1),
Enrich(*es_msg->event.exchangedata.file2)));
case ES_EVENT_TYPE_NOTIFY_EXEC:
return std::make_shared<EnrichedMessage>(EnrichedExec(
return std::make_unique<EnrichedMessage>(EnrichedExec(
std::move(es_msg), Enrich(*es_msg->process), Enrich(*es_msg->event.exec.target),
(es_msg->version >= 2 && es_msg->event.exec.script)
? std::make_optional(Enrich(*es_msg->event.exec.script))
@@ -51,28 +51,28 @@ std::shared_ptr<EnrichedMessage> Enricher::Enrich(Message &&es_msg) {
? std::make_optional(Enrich(*es_msg->event.exec.cwd))
: std::nullopt));
case ES_EVENT_TYPE_NOTIFY_FORK:
return std::make_shared<EnrichedMessage>(EnrichedFork(
return std::make_unique<EnrichedMessage>(EnrichedFork(
std::move(es_msg), Enrich(*es_msg->process), Enrich(*es_msg->event.fork.child)));
case ES_EVENT_TYPE_NOTIFY_EXIT:
return std::make_shared<EnrichedMessage>(
return std::make_unique<EnrichedMessage>(
EnrichedExit(std::move(es_msg), Enrich(*es_msg->process)));
case ES_EVENT_TYPE_NOTIFY_LINK:
return std::make_shared<EnrichedMessage>(
return std::make_unique<EnrichedMessage>(
EnrichedLink(std::move(es_msg), Enrich(*es_msg->process),
Enrich(*es_msg->event.link.source), Enrich(*es_msg->event.link.target_dir)));
case ES_EVENT_TYPE_NOTIFY_RENAME: {
if (es_msg->event.rename.destination_type == ES_DESTINATION_TYPE_NEW_PATH) {
return std::make_shared<EnrichedMessage>(EnrichedRename(
return std::make_unique<EnrichedMessage>(EnrichedRename(
std::move(es_msg), Enrich(*es_msg->process), Enrich(*es_msg->event.rename.source),
std::nullopt, Enrich(*es_msg->event.rename.destination.new_path.dir)));
} else {
return std::make_shared<EnrichedMessage>(EnrichedRename(
return std::make_unique<EnrichedMessage>(EnrichedRename(
std::move(es_msg), Enrich(*es_msg->process), Enrich(*es_msg->event.rename.source),
Enrich(*es_msg->event.rename.destination.existing_file), std::nullopt));
}
}
case ES_EVENT_TYPE_NOTIFY_UNLINK:
return std::make_shared<EnrichedMessage>(EnrichedUnlink(
return std::make_unique<EnrichedMessage>(EnrichedUnlink(
std::move(es_msg), Enrich(*es_msg->process), Enrich(*es_msg->event.unlink.target)));
default:
// This is a programming error

View File

@@ -0,0 +1,77 @@
/// Copyright 2022 Google LLC
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTAD__EVENTPROVIDERS_RATELIMITER_H
#define SANTA__SANTAD__EVENTPROVIDERS_RATELIMITER_H
#import <Foundation/Foundation.h>
#include <atomic>
#include <memory>
#include "Source/santad/Metrics.h"
// Forward declarations
namespace santa::santad::event_providers {
class RateLimiterPeer;
}
namespace santa::santad::event_providers {
// Very basic rate limiting infrastructure.
// Currently only handles X events per duration.
//
// TODO(mlw): Support changing QPS via config
// TODO(mlw): Support per-rule QPS
// TODO(mlw): Consider adding sliding window support
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);
RateLimiter(std::shared_ptr<santa::santad::Metrics> metrics,
santa::santad::Processor processor, uint16_t max_qps,
NSTimeInterval reset_duration);
enum class Decision {
kRateLimited = 0,
kAllowed,
};
Decision Decide(uint64_t cur_mach_time);
friend class santa::santad::event_providers::RateLimiterPeer;
private:
bool ShouldRateLimitLocked();
size_t EventsRateLimitedLocked();
void TryResetLocked(uint64_t cur_mach_time);
static constexpr NSTimeInterval kDefaultResetDuration = 15.0;
std::shared_ptr<santa::santad::Metrics> metrics_;
santa::santad::Processor processor_;
size_t log_count_total_ = 0;
size_t max_log_count_total_;
uint64_t reset_mach_time_;
uint64_t reset_duration_ns_;
dispatch_queue_t q_;
};
} // namespace santa::santad::event_providers
#endif

View File

@@ -0,0 +1,85 @@
/// Copyright 2022 Google LLC
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santad/EventProviders/RateLimiter.h"
#include "Source/common/BranchPrediction.h"
#include "Source/common/SystemResources.h"
using santa::santad::Metrics;
using santa::santad::Processor;
namespace santa::santad::event_providers {
std::shared_ptr<RateLimiter> RateLimiter::Create(std::shared_ptr<Metrics> metrics,
Processor processor, uint16_t max_qps,
NSTimeInterval reset_duration) {
return std::make_shared<RateLimiter>(std::move(metrics), processor, max_qps, reset_duration);
}
RateLimiter::RateLimiter(std::shared_ptr<Metrics> metrics, Processor processor, uint16_t max_qps,
NSTimeInterval reset_duration)
: metrics_(std::move(metrics)),
processor_(processor),
max_log_count_total_(reset_duration * max_qps),
reset_mach_time_(0),
reset_duration_ns_(reset_duration * NSEC_PER_SEC) {
q_ = dispatch_queue_create(
"com.google.santa.daemon.rate_limiter",
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL,
QOS_CLASS_USER_INTERACTIVE, 0));
}
bool RateLimiter::ShouldRateLimitLocked() {
return log_count_total_ > max_log_count_total_;
}
size_t RateLimiter::EventsRateLimitedLocked() {
if (unlikely(ShouldRateLimitLocked())) {
return log_count_total_ - max_log_count_total_;
} else {
return 0;
}
}
void RateLimiter::TryResetLocked(uint64_t cur_mach_time) {
if (cur_mach_time > reset_mach_time_) {
if (metrics_) {
metrics_->SetRateLimitingMetrics(processor_, EventsRateLimitedLocked());
}
log_count_total_ = 0;
reset_mach_time_ = AddNanosecondsToMachTime(reset_duration_ns_, cur_mach_time);
}
}
RateLimiter::Decision RateLimiter::Decide(uint64_t cur_mach_time) {
__block RateLimiter::Decision decision;
dispatch_sync(q_, ^{
TryResetLocked(cur_mach_time);
++log_count_total_;
if (unlikely(ShouldRateLimitLocked())) {
decision = Decision::kRateLimited;
} else {
decision = RateLimiter::Decision::kAllowed;
}
});
return decision;
}
} // namespace santa::santad::event_providers

View File

@@ -0,0 +1,149 @@
/// Copyright 2022 Google LLC
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santad/EventProviders/RateLimiter.h"
#include <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#include "Source/common/SystemResources.h"
#include "Source/santad/Metrics.h"
using santa::santad::event_providers::RateLimiter;
static const santa::santad::Processor kDefaultProcessor =
santa::santad::Processor::kFileAccessAuthorizer;
namespace santa::santad::event_providers {
class RateLimiterPeer : public RateLimiter {
public:
using RateLimiter::RateLimiter;
using RateLimiter::EventsRateLimitedLocked;
using RateLimiter::ShouldRateLimitLocked;
using RateLimiter::TryResetLocked;
using RateLimiter::log_count_total_;
using RateLimiter::reset_mach_time_;
};
} // namespace santa::santad::event_providers
using santa::santad::event_providers::RateLimiterPeer;
@interface RateLimiterTest : XCTestCase
@end
@implementation RateLimiterTest
- (void)testTryResetLocked {
// Create an object supporting 1 QPS, and a reset duration of 2s
uint16_t maxQps = 1;
NSTimeInterval resetDuration = 2;
RateLimiterPeer rlp(nullptr, kDefaultProcessor, maxQps, resetDuration);
// Check the current reset_mach_time_ is 0 so that it gets
// set when the first decision is made
XCTAssertEqual(rlp.reset_mach_time_, 0);
// Define our current mach time and create the expected new reset duration floor
uint64_t curMachTime = 1;
uint64_t expectedMachTime = AddNanosecondsToMachTime(resetDuration, curMachTime);
// Set a higher log count to ensure it is reset
rlp.log_count_total_ = 123;
rlp.TryResetLocked(curMachTime);
// Ensure values are reset appropriately
XCTAssertEqual(rlp.log_count_total_, 0);
XCTAssertGreaterThanOrEqual(rlp.reset_mach_time_, expectedMachTime);
// Setup values so that calling TryResetLocked shouldn't reset anything
size_t expectedLogCount = 123;
expectedMachTime = 456;
rlp.log_count_total_ = expectedLogCount;
rlp.reset_mach_time_ = expectedMachTime;
curMachTime = rlp.reset_mach_time_;
rlp.TryResetLocked(curMachTime);
// Ensure the values were not changed
XCTAssertEqual(rlp.log_count_total_, expectedLogCount);
XCTAssertGreaterThanOrEqual(rlp.reset_mach_time_, expectedMachTime);
}
- (void)testDecide {
// Create an object supporting 2 QPS, and a reset duration of 4s
uint16_t maxQps = 2;
NSTimeInterval resetDuration = 4;
uint64_t allowedLogsPerDuration = maxQps * resetDuration;
RateLimiterPeer rlp(nullptr, kDefaultProcessor, maxQps, resetDuration);
// Check the current log count is initially 0
XCTAssertEqual(rlp.log_count_total_, 0);
// Make the first decision
RateLimiter::Decision gotDecision;
for (uint64_t i = 0; i < (allowedLogsPerDuration); i++) {
gotDecision = rlp.Decide(0);
XCTAssertEqual(gotDecision, RateLimiter::Decision::kAllowed);
}
// Ensure the log count is the expected amount
XCTAssertEqual(rlp.log_count_total_, allowedLogsPerDuration);
// Make another decision and ensure the log count still increases and
// the decision is rate limited
gotDecision = rlp.Decide(0);
XCTAssertEqual(gotDecision, RateLimiter::Decision::kRateLimited);
XCTAssertEqual(rlp.log_count_total_, allowedLogsPerDuration + 1);
// Make another decision, though now with the cur mach time greater than
// the reset mach time. Then ensure values were appropriately reset.
uint64_t oldResetMachTime = rlp.reset_mach_time_;
gotDecision = rlp.Decide(rlp.reset_mach_time_ + 1);
XCTAssertEqual(gotDecision, RateLimiter::Decision::kAllowed);
XCTAssertEqual(rlp.log_count_total_, 1);
XCTAssertGreaterThan(rlp.reset_mach_time_, oldResetMachTime);
}
- (void)testShouldRateLimitAndCounts {
// Create an object supporting 2 QPS, and a reset duration of 4s
uint16_t maxQps = 2;
NSTimeInterval resetDuration = 4;
uint64_t allowedLogsPerDuration = maxQps * resetDuration;
uint64_t logsOverQPS = 5;
RateLimiterPeer rlp(nullptr, kDefaultProcessor, maxQps, resetDuration);
// Initially no rate limiting should apply
XCTAssertFalse(rlp.ShouldRateLimitLocked());
XCTAssertEqual(rlp.EventsRateLimitedLocked(), 0);
// Simulate a smmaller volume of logs received than QPS
rlp.log_count_total_ = allowedLogsPerDuration - 1;
XCTAssertFalse(rlp.ShouldRateLimitLocked());
XCTAssertEqual(rlp.EventsRateLimitedLocked(), 0);
// Simulate a larger volume of logs received than QPS
rlp.log_count_total_ = allowedLogsPerDuration + logsOverQPS;
XCTAssertTrue(rlp.ShouldRateLimitLocked());
XCTAssertEqual(rlp.EventsRateLimitedLocked(), logsOverQPS);
}
@end

View File

@@ -85,34 +85,41 @@ class MockAuthResultCache : public AuthResultCache {
}
- (void)testHandleMessage {
#ifdef THREAD_SANITIZER
// TSAN and this test do not get along in multiple ways.
// We get data race false positives in OCMock, and timeouts
// waiting for messages processing (presumably due to tsan's scheduling).
// Just skip it.
XCTSkip(@"TSAN enabled");
return;
#endif
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);
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
// There is a benign leak of the mock object in this test.
// `handleMessage:recordEventMetrics:` will call `processMessage:handler:` in the parent
// class. This will dispatch to two blocks and create message copies. The block that
// handles `deadline` timeouts will not complete before the test finishes, and the
// mock object will think that it has been leaked.
::testing::Mock::AllowLeak(mockESApi.get());
dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0);
SNTEndpointSecurityAuthorizer *authClient =
[[SNTEndpointSecurityAuthorizer alloc] initWithESAPI:mockESApi
metrics:nullptr
execController:self.mockExecController
compilerController:nil
authResultCache:nullptr];
id mockAuthClient = OCMPartialMock(authClient);
// Test unhandled event type
{
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
// There is a benign leak of the mock object in this test.
// `handleMessage:recordEventMetrics:` will call `processMessage:handler:` in the parent
// class. This will dispatch to two blocks and create message copies. The block that
// handles `deadline` timeouts will not complete before the test finishes, and the
// mock object will think that it has been leaked.
::testing::Mock::AllowLeak(mockESApi.get());
SNTEndpointSecurityAuthorizer *authClient =
[[SNTEndpointSecurityAuthorizer alloc] initWithESAPI:mockESApi
metrics:nullptr
execController:self.mockExecController
compilerController:nil
authResultCache:nullptr];
// Temporarily change the event type
esMsg.event_type = ES_EVENT_TYPE_NOTIFY_EXEC;
XCTAssertThrows([authClient handleMessage:Message(mockESApi, &esMsg)
@@ -120,60 +127,97 @@ class MockAuthResultCache : public AuthResultCache {
XCTFail("Unhandled event types shouldn't call metrics recorder");
}]);
esMsg.event_type = ES_EVENT_TYPE_AUTH_EXEC;
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
// Test SNTExecutionController determines the event shouldn't be processed
{
Message msg(mockESApi, &esMsg);
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
::testing::Mock::AllowLeak(mockESApi.get());
OCMExpect([self.mockExecController synchronousShouldProcessExecEvent:msg])
.ignoringNonObjectArgs()
.andReturn(NO);
SNTEndpointSecurityAuthorizer *authClient =
[[SNTEndpointSecurityAuthorizer alloc] initWithESAPI:mockESApi
metrics:nullptr
execController:self.mockExecController
compilerController:nil
authResultCache:nullptr];
OCMExpect([mockAuthClient postAction:SNTActionRespondDeny
id mockAuthClient = OCMPartialMock(authClient);
// Scope so msg is destructed (and calls ReleaseMessage) before stopMocking is called.
{
Message msg(mockESApi, &esMsg);
OCMExpect([self.mockExecController synchronousShouldProcessExecEvent:msg])
.ignoringNonObjectArgs()
.andReturn(NO);
OCMExpect([mockAuthClient postAction:SNTActionRespondDeny
forMessage:Message(mockESApi, &esMsg)])
.ignoringNonObjectArgs();
OCMStub([mockAuthClient postAction:SNTActionRespondDeny
forMessage:Message(mockESApi, &esMsg)])
.ignoringNonObjectArgs();
OCMStub([mockAuthClient postAction:SNTActionRespondDeny forMessage:Message(mockESApi, &esMsg)])
.ignoringNonObjectArgs()
.andDo(nil);
.ignoringNonObjectArgs()
.andDo(nil);
[mockAuthClient handleMessage:std::move(msg)
recordEventMetrics:^(EventDisposition d) {
XCTAssertEqual(d, EventDisposition::kDropped);
dispatch_semaphore_signal(semaMetrics);
}];
[mockAuthClient handleMessage:std::move(msg)
recordEventMetrics:^(EventDisposition d) {
XCTAssertEqual(d, EventDisposition::kDropped);
dispatch_semaphore_signal(semaMetrics);
}];
XCTAssertSemaTrue(semaMetrics, 5, "Metrics not recorded within expected window");
XCTAssertTrue(OCMVerifyAll(mockAuthClient));
XCTAssertSemaTrue(semaMetrics, 5, "Metrics not recorded within expected window");
XCTAssertTrue(OCMVerifyAll(mockAuthClient));
}
[mockAuthClient stopMocking];
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
// Test SNTExecutionController determines the event should be processed and
// processMessage:handler: is called.
{
Message msg(mockESApi, &esMsg);
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
::testing::Mock::AllowLeak(mockESApi.get());
OCMExpect([self.mockExecController synchronousShouldProcessExecEvent:msg])
.ignoringNonObjectArgs()
.andReturn(YES);
SNTEndpointSecurityAuthorizer *authClient =
[[SNTEndpointSecurityAuthorizer alloc] initWithESAPI:mockESApi
metrics:nullptr
execController:self.mockExecController
compilerController:nil
authResultCache:nullptr];
OCMExpect([mockAuthClient processMessage:Message(mockESApi, &esMsg)]).ignoringNonObjectArgs();
OCMStub([mockAuthClient processMessage:Message(mockESApi, &esMsg)])
.ignoringNonObjectArgs()
.andDo(nil);
id mockAuthClient = OCMPartialMock(authClient);
[mockAuthClient handleMessage:std::move(msg)
recordEventMetrics:^(EventDisposition d) {
XCTAssertEqual(d, EventDisposition::kProcessed);
dispatch_semaphore_signal(semaMetrics);
}];
{
Message msg(mockESApi, &esMsg);
XCTAssertSemaTrue(semaMetrics, 5, "Metrics not recorded within expected window");
XCTAssertTrue(OCMVerifyAll(mockAuthClient));
OCMExpect([self.mockExecController synchronousShouldProcessExecEvent:msg])
.ignoringNonObjectArgs()
.andReturn(YES);
OCMExpect([mockAuthClient processMessage:Message(mockESApi, &esMsg)]).ignoringNonObjectArgs();
OCMStub([mockAuthClient processMessage:Message(mockESApi, &esMsg)])
.ignoringNonObjectArgs()
.andDo(nil);
[mockAuthClient handleMessage:std::move(msg)
recordEventMetrics:^(EventDisposition d) {
XCTAssertEqual(d, EventDisposition::kProcessed);
dispatch_semaphore_signal(semaMetrics);
}];
XCTAssertSemaTrue(semaMetrics, 5, "Metrics not recorded within expected window");
XCTAssertTrue(OCMVerifyAll(mockAuthClient));
}
[mockAuthClient stopMocking];
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
[mockAuthClient stopMocking];
}
- (void)testProcessMessageWaitThenAllow {
@@ -190,7 +234,7 @@ class MockAuthResultCache : public AuthResultCache {
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, CheckCache)
.WillOnce(testing::Return(SNTActionRequestBinary))
.WillOnce(testing::Return(SNTActionRequestBinary))
@@ -257,7 +301,7 @@ class MockAuthResultCache : public AuthResultCache {
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, AddToCache(&execFile, SNTActionRespondAllowCompiler))
.WillOnce(testing::Return(true));
EXPECT_CALL(*mockAuthCache, AddToCache(&execFile, SNTActionRespondAllow))

View File

@@ -49,6 +49,7 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
@interface SNTEndpointSecurityClient ()
@property int64_t deadlineMarginMS;
@property SNTConfigurator *configurator;
@end
@implementation SNTEndpointSecurityClient {
@@ -68,6 +69,7 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
_esApi = std::move(esApi);
_metrics = std::move(metrics);
_deadlineMarginMS = 5000;
_configurator = [SNTConfigurator configurator];
_processor = processor;
_authQueue = dispatch_queue_create(
@@ -103,9 +105,8 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
[self doesNotRecognizeSelector:_cmd];
}
- (BOOL)shouldHandleMessage:(const Message &)esMsg
ignoringOtherESClients:(BOOL)ignoringOtherESClients {
if (esMsg->process->is_es_client && ignoringOtherESClients) {
- (BOOL)shouldHandleMessage:(const Message &)esMsg {
if (esMsg->process->is_es_client && [self.configurator ignoreOtherEndpointSecurityClients]) {
if (esMsg->action_type == ES_ACTION_TYPE_AUTH) {
[self respondToMessage:esMsg withAuthResult:ES_AUTH_RESULT_ALLOW cacheable:true];
}
@@ -125,9 +126,7 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
self->_esClient = self->_esApi->NewClient(^(es_client_t *c, Message esMsg) {
int64_t processingStart = clock_gettime_nsec_np(CLOCK_MONOTONIC);
es_event_type_t eventType = esMsg->event_type;
if ([self shouldHandleMessage:esMsg
ignoringOtherESClients:[[SNTConfigurator configurator]
ignoreOtherEndpointSecurityClients]]) {
if ([self shouldHandleMessage:esMsg]) {
[self handleMessage:std::move(esMsg)
recordEventMetrics:^(EventDisposition disposition) {
int64_t processingEnd = clock_gettime_nsec_np(CLOCK_MONOTONIC);
@@ -194,13 +193,12 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
return _esApi->UnsubscribeAll(_esClient);
}
- (bool)unmuteEverything {
bool result = _esApi->UnmuteAllPaths(_esClient);
result = _esApi->UnmuteAllTargetPaths(_esClient) && result;
return result;
- (bool)unmuteAllTargetPaths {
return _esApi->UnmuteAllTargetPaths(_esClient);
}
- (bool)enableTargetPathWatching {
[self unmuteAllTargetPaths];
return _esApi->InvertTargetPathMuting(_esClient);
}
@@ -237,9 +235,9 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
}
}
- (void)processEnrichedMessage:(std::shared_ptr<EnrichedMessage>)msg
handler:(void (^)(std::shared_ptr<EnrichedMessage>))messageHandler {
__block std::shared_ptr<EnrichedMessage> msgTmp = std::move(msg);
- (void)processEnrichedMessage:(std::unique_ptr<EnrichedMessage>)msg
handler:(void (^)(std::unique_ptr<EnrichedMessage>))messageHandler {
__block std::unique_ptr<EnrichedMessage> msgTmp = std::move(msg);
dispatch_async(_notifyQueue, ^{
messageHandler(std::move(msgTmp));
});

View File

@@ -49,7 +49,7 @@
- (bool)subscribeAndClearCache:(const std::set<es_event_type_t> &)events;
- (bool)unsubscribeAll;
- (bool)unmuteEverything;
- (bool)unmuteAllTargetPaths;
- (bool)enableTargetPathWatching;
- (bool)muteTargetPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
@@ -72,9 +72,9 @@
- (void)
processEnrichedMessage:
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage>)msg
(std::unique_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage>)msg
handler:
(void (^)(std::shared_ptr<
(void (^)(std::unique_ptr<
santa::santad::event_providers::endpoint_security::EnrichedMessage>))
messageHandler;

View File

@@ -22,6 +22,7 @@
#include <memory>
#import "Source/common/SNTConfigurator.h"
#include "Source/common/TestUtils.h"
#include "Source/santad/DataLayer/WatchItemPolicy.h"
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
@@ -46,8 +47,7 @@ using santa::santad::event_providers::endpoint_security::Message;
- (NSString *)errorMessageForNewClientResult:(es_new_client_result_t)result;
- (void)handleMessage:(Message &&)esMsg
recordEventMetrics:(void (^)(santa::santad::EventDisposition disposition))recordEventMetrics;
- (BOOL)shouldHandleMessage:(const Message &)esMsg
ignoringOtherESClients:(BOOL)ignoringOtherESClients;
- (BOOL)shouldHandleMessage:(const Message &)esMsg;
@property int64_t deadlineMarginMS;
@end
@@ -130,6 +130,9 @@ using santa::santad::event_providers::endpoint_security::Message;
EXPECT_CALL(*mockESApi, RespondAuthResult(testing::_, testing::_, ES_AUTH_RESULT_ALLOW, true))
.WillOnce(testing::Return(true));
id mockConfigurator = OCMStrictClassMock([SNTConfigurator class]);
OCMStub([mockConfigurator configurator]).andReturn(mockConfigurator);
SNTEndpointSecurityClient *client =
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
metrics:nullptr
@@ -139,24 +142,31 @@ using santa::santad::event_providers::endpoint_security::Message;
Message msg(mockESApi, &esMsg);
// Is ES client, but don't ignore others == Should Handle
OCMExpect([mockConfigurator ignoreOtherEndpointSecurityClients]).andReturn(NO);
esMsg.process->is_es_client = true;
XCTAssertTrue([client shouldHandleMessage:msg ignoringOtherESClients:NO]);
XCTAssertTrue([client shouldHandleMessage:msg]);
// Not ES client, but ignore others == Should Handle
// Don't setup configurator mock since it won't be called when `is_es_client` is false
esMsg.process->is_es_client = false;
XCTAssertTrue([client shouldHandleMessage:msg ignoringOtherESClients:YES]);
XCTAssertTrue([client shouldHandleMessage:msg]);
// Is ES client, don't ignore others, and non-AUTH == Don't Handle
OCMExpect([mockConfigurator ignoreOtherEndpointSecurityClients]).andReturn(YES);
esMsg.process->is_es_client = true;
XCTAssertFalse([client shouldHandleMessage:msg ignoringOtherESClients:YES]);
XCTAssertFalse([client shouldHandleMessage:msg]);
// Is ES client, don't ignore others, and AUTH == Respond and Don't Handle
OCMExpect([mockConfigurator ignoreOtherEndpointSecurityClients]).andReturn(YES);
esMsg.process->is_es_client = true;
esMsg.action_type = ES_ACTION_TYPE_AUTH;
XCTAssertFalse([client shouldHandleMessage:msg ignoringOtherESClients:YES]);
XCTAssertFalse([client shouldHandleMessage:msg]);
}
XCTAssertTrue(OCMVerifyAll(mockConfigurator));
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
[mockConfigurator stopMocking];
}
- (void)testPopulateAuditTokenSelf {
@@ -264,23 +274,20 @@ using santa::santad::event_providers::endpoint_security::Message;
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
- (void)testUnmuteEverything {
- (void)testUnmuteAllTargetPaths {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
SNTEndpointSecurityClient *client =
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
metrics:nullptr
processor:Processor::kUnknown];
// Test variations of underlying unmute impls returning both true and false
EXPECT_CALL(*mockESApi, UnmuteAllPaths)
.WillOnce(testing::Return(true))
.WillOnce(testing::Return(false));
// Test the underlying unmute impl returning both true and false
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths)
.WillOnce(testing::Return(true))
.WillOnce(testing::Return(true));
.WillOnce(testing::Return(false));
XCTAssertTrue([client unmuteEverything]);
XCTAssertFalse([client unmuteEverything]);
XCTAssertTrue([client unmuteAllTargetPaths]);
XCTAssertFalse([client unmuteAllTargetPaths]);
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
@@ -292,6 +299,9 @@ using santa::santad::event_providers::endpoint_security::Message;
metrics:nullptr
processor:Processor::kUnknown];
// UnmuteAllTargetPaths is always attempted.
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).Times(2).WillRepeatedly(testing::Return(true));
// Test the underlying invert nute impl returning both true and false
EXPECT_CALL(*mockESApi, InvertTargetPathMuting)
.WillOnce(testing::Return(true))
@@ -359,6 +369,8 @@ using santa::santad::event_providers::endpoint_security::Message;
- (void)testRespondToMessageWithAuthResultCacheable {
es_message_t esMsg;
esMsg.event_type = ES_EVENT_TYPE_AUTH_EXEC;
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
mockESApi->SetExpectationsRetainReleaseMessage();
@@ -394,14 +406,21 @@ using santa::santad::event_providers::endpoint_security::Message;
metrics:nullptr
processor:Processor::kUnknown];
{
auto enrichedMsg = std::make_shared<EnrichedMessage>(
auto enrichedMsg = std::make_unique<EnrichedMessage>(
EnrichedClose(Message(mockESApi, &esMsg),
EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt,
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)),
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)));
[client processEnrichedMessage:enrichedMsg
handler:^(std::shared_ptr<EnrichedMessage> msg) {
[client processEnrichedMessage:std::move(enrichedMsg)
handler:^(std::unique_ptr<EnrichedMessage> msg) {
// reset the shared_ptr to drop the held message.
// This is a workaround for a TSAN only false positive
// which happens if we switch back to the sem wait
// after signaling, but _before_ the implicit release
// of msg. In that case, the mock verify and the
// call of the mock's Release method can data race.
msg.reset();
dispatch_semaphore_signal(sema);
}];

View File

@@ -33,6 +33,7 @@
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;
@@ -172,6 +173,15 @@ NS_ASSUME_NONNULL_BEGIN
- (void)handleMessage:(Message &&)esMsg
recordEventMetrics:(void (^)(EventDisposition))recordEventMetrics {
// Process the unmount event first so that caches are flushed before any
// other potential early returns.
if (esMsg->event_type == ES_EVENT_TYPE_NOTIFY_UNMOUNT) {
self->_authResultCache->FlushCache(FlushCacheMode::kNonRootOnly,
FlushCacheReason::kFilesystemUnmounted);
recordEventMetrics(EventDisposition::kProcessed);
return;
}
if (!self.blockUSBMount) {
// TODO: We should also unsubscribe from events when this isn't set, but
// this is generally a low-volume event type.
@@ -180,12 +190,6 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
if (esMsg->event_type == ES_EVENT_TYPE_NOTIFY_UNMOUNT) {
self->_authResultCache->FlushCache(FlushCacheMode::kNonRootOnly);
recordEventMetrics(EventDisposition::kProcessed);
return;
}
[self processMessage:std::move(esMsg)
handler:^(const Message &msg) {
es_auth_result_t result = [self handleAuthMount:msg];
@@ -248,10 +252,17 @@ NS_ASSUME_NONNULL_BEGIN
@"isEjectable: %d",
protocol, kind, isInternal, isRemovable, isEjectable);
// If the device is internal or virtual we are okay with the operation. We
// also are okay with operations for devices that are non-removal as long as
// if the device is internal, or virtual *AND* is not an SD Card,
// then allow the mount. This is to ensure we block SD cards inserted into
// the internal reader of some Macs, whilst also ensuring we don't block
// the internal storage device.
if ((isInternal || isVirtual) && !isSecureDigital) {
return ES_AUTH_RESULT_ALLOW;
}
// We are okay with operations for devices that are non-removable as long as
// they are NOT a USB device, or an SD Card.
if (isInternal || isVirtual || (!isRemovable && !isEjectable && !isUSB && !isSecureDigital)) {
if (!isRemovable && !isEjectable && !isUSB && !isSecureDigital) {
return ES_AUTH_RESULT_ALLOW;
}

View File

@@ -39,13 +39,14 @@
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;
class MockAuthResultCache : public AuthResultCache {
public:
using AuthResultCache::AuthResultCache;
MOCK_METHOD(void, FlushCache, (FlushCacheMode mode));
MOCK_METHOD(void, FlushCache, (FlushCacheMode mode, FlushCacheReason reason));
};
@interface SNTEndpointSecurityDeviceManager (Testing)
@@ -316,7 +317,7 @@ class MockAuthResultCache : public AuthResultCache {
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, FlushCache);
SNTEndpointSecurityDeviceManager *deviceManager =

View File

@@ -32,6 +32,8 @@
#include "Source/common/Platform.h"
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTStrengthify.h"
#include "Source/common/SantaCache.h"
#include "Source/common/SantaVnode.h"
#include "Source/common/SantaVnodeHash.h"
@@ -39,11 +41,13 @@
#include "Source/santad/DataLayer/WatchItems.h"
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/EventProviders/RateLimiter.h"
using santa::santad::EventDisposition;
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;
@@ -53,6 +57,7 @@ using santa::santad::logs::endpoint_security::Logger;
NSString *kBadCertHash = @"BAD_CERT_HASH";
static constexpr uint32_t kOpenFlagsIndicatingWrite = FWRITE | O_APPEND | O_TRUNC;
static constexpr uint16_t kDefaultRateLimitQPS = 50;
// Small structure to hold a complete event path target being operated upon and
// a bool indicating whether the path is a readable target (e.g. a file being
@@ -196,6 +201,7 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
std::shared_ptr<Logger> _logger;
std::shared_ptr<WatchItems> _watchItems;
std::shared_ptr<Enricher> _enricher;
std::shared_ptr<RateLimiter> _rateLimiter;
SantaCache<SantaVnode, NSString *> _certHashCache;
}
@@ -209,7 +215,7 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
decisionCache:(SNTDecisionCache *)decisionCache {
self = [super initWithESAPI:std::move(esApi)
metrics:std::move(metrics)
metrics:metrics
processor:santa::santad::Processor::kFileAccessAuthorizer];
if (self) {
_watchItems = std::move(watchItems);
@@ -218,10 +224,23 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
_decisionCache = decisionCache;
_rateLimiter = RateLimiter::Create(metrics, santa::santad::Processor::kFileAccessAuthorizer,
kDefaultRateLimitQPS);
SNTMetricBooleanGauge *famEnabled = [[SNTMetricSet sharedInstance]
booleanGaugeWithName:@"/santa/fam_enabled"
fieldNames:@[]
helpText:@"Whether or not the FAM client is enabled"];
WEAKIFY(self);
[[SNTMetricSet sharedInstance] registerCallback:^{
STRONGIFY(self);
[famEnabled set:self.isSubscribed forFieldValues:@[]];
}];
[self establishClientOrDie];
[super enableTargetPathWatching];
[super unmuteEverything];
}
return self;
}
@@ -319,14 +338,24 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
// configured process exception while the check for a valid code signature
// is more broad and applies whether or not process exceptions exist.
if (esProc->codesigning_flags & CS_SIGNED) {
// Check if the instigating process has an allowed TeamID
if (!policyProc.team_id.empty() && esProc->team_id.data &&
policyProc.team_id != esProc->team_id.data) {
// Check whether or not the process is a platform binary if specified by the policy.
if (policyProc.platform_binary.has_value() &&
policyProc.platform_binary.value() != esProc->is_platform_binary) {
return false;
}
if (!policyProc.signing_id.empty() && esProc->signing_id.data &&
policyProc.signing_id != esProc->signing_id.data) {
// If the policy contains a team ID, check that the instigating process
// also has a team ID and matches the policy.
if (!policyProc.team_id.empty() &&
(!esProc->team_id.data || (policyProc.team_id != esProc->team_id.data))) {
// We expected a team ID to match against, but the process didn't have one.
return false;
}
// If the policy contains a signing ID, check that the instigating process
// also has a signing ID and matches the policy.
if (!policyProc.signing_id.empty() &&
(!esProc->signing_id.data || (policyProc.signing_id != esProc->signing_id.data))) {
return false;
}
@@ -396,19 +425,35 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
return specialCase;
}
FileAccessPolicyDecision decision = FileAccessPolicyDecision::kDenied;
for (const WatchItemPolicy::Process &process : policy->processes) {
if ([self policyProcess:process matchesESProcess:msg->process]) {
return FileAccessPolicyDecision::kAllowed;
decision = FileAccessPolicyDecision::kAllowed;
break;
}
}
if (policy->audit_only) {
return FileAccessPolicyDecision::kAllowedAuditOnly;
} else {
// TODO(xyz): Write to TTY like in exec controller?
// TODO(xyz): Need new config item for custom message in UI
return FileAccessPolicyDecision::kDenied;
// If the `invert_process_exceptions` option is set, the decision should be
// inverted from allowed to denied or vice versa. Note that this inversion
// must be made prior to checking the policy's audit-only flag.
if (policy->invert_process_exceptions) {
if (decision == FileAccessPolicyDecision::kAllowed) {
decision = FileAccessPolicyDecision::kDenied;
} else {
decision = FileAccessPolicyDecision::kAllowed;
}
}
if (decision == FileAccessPolicyDecision::kDenied && policy->audit_only) {
decision = FileAccessPolicyDecision::kAllowedAuditOnly;
}
// https://github.com/google/santa/issues/1084
// TODO(xyz): Write to TTY like in exec controller?
// TODO(xyz): Need new config item for custom message in UI
return decision;
}
- (FileAccessPolicyDecision)handleMessage:(const Message &)msg
@@ -420,8 +465,10 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
forTarget:target
toMessage:msg];
if (ShouldLogDecision(policyDecision)) {
if (optionalPolicy.has_value()) {
// Note: If ShouldLogDecision, it shouldn't be possible for optionalPolicy
// to not have a value. Performing the check just in case to prevent a crash.
if (ShouldLogDecision(policyDecision) && optionalPolicy.has_value()) {
if (_rateLimiter->Decide(msg->mach_time) == RateLimiter::Decision::kAllowed) {
std::string policyNameCopy = optionalPolicy.value()->name;
std::string policyVersionCopy = policyVersion;
std::string targetPathCopy = target.path;
@@ -433,10 +480,6 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
self->_enricher->Enrich(*esMsg->process, EnrichOptions::kLocalOnly),
targetPathCopy, policyDecision);
}];
} else {
LOGE(@"Unexpectedly missing policy: Unable to log file access event: %s -> %s",
Path(msg->process->executable).data(), target.path.c_str());
}
}
@@ -512,9 +555,13 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
#endif
if (!self.isSubscribed) {
self.isSubscribed = [super subscribe:events];
[super clearCache];
if ([super subscribe:events]) {
self.isSubscribed = true;
}
}
// Always clear cache to ensure operations that were previously allowed are re-evaluated.
[super clearCache];
}
- (void)disable {
@@ -522,7 +569,7 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
if ([super unsubscribeAll]) {
self.isSubscribed = false;
}
[super unmuteEverything];
[super unmuteAllTargetPaths];
}
}

View File

@@ -61,7 +61,6 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
void SetExpectationsForFileAccessAuthorizerInit(
std::shared_ptr<MockEndpointSecurityAPI> mockESApi) {
EXPECT_CALL(*mockESApi, InvertTargetPathMuting).WillOnce(testing::Return(true));
EXPECT_CALL(*mockESApi, UnmuteAllPaths).WillOnce(testing::Return(true));
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).WillOnce(testing::Return(true));
}
@@ -384,6 +383,7 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
esProc.codesigning_flags = CS_SIGNED;
esProc.team_id = MakeESStringToken(teamId);
esProc.signing_id = MakeESStringToken(signingId);
esProc.is_platform_binary = true;
std::memcpy(esProc.cdhash, cdhashBytes.data(), sizeof(esProc.cdhash));
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
@@ -405,7 +405,7 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
.ignoringNonObjectArgs()
.andReturn(@(instigatingCertHash));
WatchItemPolicy::Process policyProc("", "", "", {}, "");
WatchItemPolicy::Process policyProc("", "", "", {}, "", std::nullopt);
{
// Process policy matching single attribute - path
@@ -423,6 +423,11 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
XCTAssertTrue([accessClient policyProcess:policyProc matchesESProcess:&esProc]);
policyProc.signing_id = "badid";
XCTAssertFalse([accessClient policyProcess:policyProc matchesESProcess:&esProc]);
es_process_t esProcEmptySigningID = MakeESProcess(&esFile);
esProcEmptySigningID.codesigning_flags = CS_SIGNED;
esProcEmptySigningID.team_id.data = NULL;
esProcEmptySigningID.team_id.length = 0;
XCTAssertFalse([accessClient policyProcess:policyProc matchesESProcess:&esProcEmptySigningID]);
}
{
@@ -432,6 +437,11 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
XCTAssertTrue([accessClient policyProcess:policyProc matchesESProcess:&esProc]);
policyProc.team_id = "badid";
XCTAssertFalse([accessClient policyProcess:policyProc matchesESProcess:&esProc]);
es_process_t esProcEmptyTeamID = MakeESProcess(&esFile);
esProcEmptyTeamID.codesigning_flags = CS_SIGNED;
esProcEmptyTeamID.signing_id.data = NULL;
esProcEmptyTeamID.signing_id.length = 0;
XCTAssertFalse([accessClient policyProcess:policyProc matchesESProcess:&esProcEmptyTeamID]);
}
{
@@ -452,6 +462,15 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
XCTAssertFalse([accessClient policyProcess:policyProc matchesESProcess:&esProc]);
}
{
// Process policy matching single attribute - platform binary
ClearWatchItemPolicyProcess(policyProc);
policyProc.platform_binary = std::make_optional(true);
XCTAssertTrue([accessClient policyProcess:policyProc matchesESProcess:&esProc]);
policyProc.platform_binary = std::make_optional(false);
XCTAssertFalse([accessClient policyProcess:policyProc matchesESProcess:&esProc]);
}
{
// Process policy with only a subset of matching attributes
ClearWatchItemPolicyProcess(policyProc);
@@ -476,7 +495,7 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
const char *instigatingPath = "/path/to/proc";
const char *instigatingTeamID = "my_teamid";
const char *instigatingCertHash = "abc123";
WatchItemPolicy::Process policyProc(instigatingPath, "", "", {}, "");
WatchItemPolicy::Process policyProc(instigatingPath, "", "", {}, "", std::nullopt);
std::array<uint8_t, 20> instigatingCDHash;
instigatingCDHash.fill(0x41);
es_file_t esFile = MakeESFile(instigatingPath);
@@ -512,8 +531,9 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
// If no policy exists, the operation is allowed
{
Message msg(mockESApi, &esMsg);
XCTAssertEqual([accessClient applyPolicy:std::nullopt forTarget:target toMessage:msg],
XCTAssertEqual([accessClient applyPolicy:std::nullopt
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kNoPolicy);
}
@@ -526,8 +546,9 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
{
OCMExpect([self.mockConfigurator enableBadSignatureProtection]).andReturn(YES);
esMsg.process->codesigning_flags = CS_SIGNED;
Message msg(mockESApi, &esMsg);
XCTAssertEqual([accessClient applyPolicy:optionalPolicy forTarget:target toMessage:msg],
XCTAssertEqual([accessClient applyPolicy:optionalPolicy
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kDeniedInvalidSignature);
}
@@ -537,11 +558,12 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
{
OCMExpect([self.mockConfigurator enableBadSignatureProtection]).andReturn(NO);
esMsg.process->codesigning_flags = CS_SIGNED;
Message msg(mockESApi, &esMsg);
OCMExpect([accessClientMock policyProcess:policyProc matchesESProcess:&esProc])
.ignoringNonObjectArgs()
.andReturn(true);
XCTAssertEqual([accessClient applyPolicy:optionalPolicy forTarget:target toMessage:msg],
XCTAssertEqual([accessClient applyPolicy:optionalPolicy
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kAllowed);
}
@@ -554,8 +576,9 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
.ignoringNonObjectArgs()
.andReturn(false);
policy->audit_only = false;
Message msg(mockESApi, &esMsg);
XCTAssertEqual([accessClient applyPolicy:optionalPolicy forTarget:target toMessage:msg],
XCTAssertEqual([accessClient applyPolicy:optionalPolicy
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kDenied);
}
@@ -565,8 +588,50 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
.ignoringNonObjectArgs()
.andReturn(false);
policy->audit_only = true;
Message msg(mockESApi, &esMsg);
XCTAssertEqual([accessClient applyPolicy:optionalPolicy forTarget:target toMessage:msg],
XCTAssertEqual([accessClient applyPolicy:optionalPolicy
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kAllowedAuditOnly);
}
// The remainder of the tests set the policy's `invert_process_exceptions` option
policy->invert_process_exceptions = true;
// If no exceptions for inverted policy, operations are allowed
{
OCMExpect([accessClientMock policyProcess:policyProc matchesESProcess:&esProc])
.ignoringNonObjectArgs()
.andReturn(false);
policy->audit_only = false;
XCTAssertEqual([accessClient applyPolicy:optionalPolicy
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kAllowed);
}
// For audit only policies with no exception matches and inverted exceptions, operations are
// allowed
{
OCMExpect([accessClientMock policyProcess:policyProc matchesESProcess:&esProc])
.ignoringNonObjectArgs()
.andReturn(false);
policy->audit_only = true;
XCTAssertEqual([accessClient applyPolicy:optionalPolicy
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kAllowed);
}
// For audit only policies with exception match and inverted exceptions, operations are allowed
// audit only
{
OCMExpect([accessClientMock policyProcess:policyProc matchesESProcess:&esProc])
.ignoringNonObjectArgs()
.andReturn(true);
policy->audit_only = true;
XCTAssertEqual([accessClient applyPolicy:optionalPolicy
forTarget:target
toMessage:Message(mockESApi, &esMsg)],
FileAccessPolicyDecision::kAllowedAuditOnly);
}
@@ -616,7 +681,6 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
decisionCache:nil];
EXPECT_CALL(*mockESApi, UnsubscribeAll);
EXPECT_CALL(*mockESApi, UnmuteAllPaths).WillOnce(testing::Return(true));
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).WillOnce(testing::Return(true));
accessClient.isSubscribed = true;

View File

@@ -110,11 +110,11 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
// Enrich the message inline with the ES handler block to capture enrichment
// data as close to the source event as possible.
std::shared_ptr<EnrichedMessage> sharedEnrichedMessage = _enricher->Enrich(std::move(esMsg));
std::unique_ptr<EnrichedMessage> enrichedMessage = _enricher->Enrich(std::move(esMsg));
// Asynchronously log the message
[self processEnrichedMessage:std::move(sharedEnrichedMessage)
handler:^(std::shared_ptr<EnrichedMessage> msg) {
[self processEnrichedMessage:std::move(enrichedMessage)
handler:^(std::unique_ptr<EnrichedMessage> msg) {
self->_logger->Log(std::move(msg));
recordEventMetrics(EventDisposition::kProcessed);
}];

View File

@@ -48,7 +48,7 @@ using santa::santad::logs::endpoint_security::Logger;
class MockEnricher : public Enricher {
public:
MOCK_METHOD(std::shared_ptr<EnrichedMessage>, Enrich, (Message &&));
MOCK_METHOD(std::unique_ptr<EnrichedMessage>, Enrich, (Message &&));
};
class MockAuthResultCache : public AuthResultCache {
@@ -62,7 +62,7 @@ class MockLogger : public Logger {
public:
using Logger::Logger;
MOCK_METHOD(void, Log, (std::shared_ptr<EnrichedMessage>));
MOCK_METHOD(void, Log, (std::unique_ptr<EnrichedMessage>));
};
@interface SNTEndpointSecurityRecorderTest : XCTestCase
@@ -100,12 +100,12 @@ class MockLogger : public Logger {
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
std::shared_ptr<EnrichedMessage> enrichedMsg = std::shared_ptr<EnrichedMessage>(nullptr);
std::unique_ptr<EnrichedMessage> enrichedMsg = std::unique_ptr<EnrichedMessage>(nullptr);
auto mockEnricher = std::make_shared<MockEnricher>();
EXPECT_CALL(*mockEnricher, Enrich).WillOnce(testing::Return(enrichedMsg));
EXPECT_CALL(*mockEnricher, Enrich).WillOnce(testing::Return(std::move(enrichedMsg)));
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, RemoveFromCache(&targetFile)).Times(1);
dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0);

View File

@@ -111,7 +111,6 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
- (void)enable {
[super enableTargetPathWatching];
[super unmuteEverything];
// Get the set of protected paths
std::set<std::string> protectedPaths = [SNTEndpointSecurityTamperResistance getProtectedPaths];

View File

@@ -65,7 +65,6 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
// Setup mocks to handle inverting target path muting
EXPECT_CALL(*mockESApi, InvertTargetPathMuting).WillOnce(testing::Return(true));
EXPECT_CALL(*mockESApi, UnmuteAllPaths).WillOnce(testing::Return(true));
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).WillOnce(testing::Return(true));
// Setup mocks to handle muting the rules db and events db

View File

@@ -26,6 +26,7 @@
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Serializer.h"
#include "Source/santad/Logs/EndpointSecurity/Writers/Writer.h"
#import "Source/santad/SNTDecisionCache.h"
// Forward declarations
@class SNTStoredEvent;
@@ -39,8 +40,8 @@ class Logger {
public:
static std::unique_ptr<Logger> Create(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
SNTEventLogType log_type, NSString *event_log_path, NSString *spool_log_path,
size_t spool_dir_size_threshold, size_t spool_file_size_threshold,
SNTEventLogType log_type, SNTDecisionCache *decision_cache, NSString *event_log_path,
NSString *spool_log_path, size_t spool_dir_size_threshold, size_t spool_file_size_threshold,
uint64_t spool_flush_timeout_ms);
Logger(std::shared_ptr<serializers::Serializer> serializer,
@@ -49,7 +50,7 @@ class Logger {
virtual ~Logger() = default;
virtual void Log(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage> msg);
std::unique_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage> msg);
void LogAllowlist(const santa::santad::event_providers::endpoint_security::Message &msg,
const std::string_view hash);
@@ -65,6 +66,8 @@ class Logger {
const santa::santad::event_providers::endpoint_security::EnrichedProcess &enriched_process,
const std::string &target, FileAccessPolicyDecision decision);
void Flush();
friend class santa::santad::logs::endpoint_security::LoggerPeer;
private:

View File

@@ -20,10 +20,12 @@
#include "Source/santad/Logs/EndpointSecurity/Serializers/BasicString.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Empty.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Serializer.h"
#include "Source/santad/Logs/EndpointSecurity/Writers/File.h"
#include "Source/santad/Logs/EndpointSecurity/Writers/Null.h"
#include "Source/santad/Logs/EndpointSecurity/Writers/Spool.h"
#include "Source/santad/Logs/EndpointSecurity/Writers/Syslog.h"
#include "Source/santad/SNTDecisionCache.h"
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
@@ -48,24 +50,26 @@ static const size_t kMaxExpectedWriteSizeBytes = 4096;
// Translate configured log type to appropriate Serializer/Writer pairs
std::unique_ptr<Logger> Logger::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
SNTEventLogType log_type, NSString *event_log_path,
NSString *spool_log_path, size_t spool_dir_size_threshold,
SNTEventLogType log_type, SNTDecisionCache *decision_cache,
NSString *event_log_path, NSString *spool_log_path,
size_t spool_dir_size_threshold,
size_t spool_file_size_threshold,
uint64_t spool_flush_timeout_ms) {
switch (log_type) {
case SNTEventLogTypeFilelog:
return std::make_unique<Logger>(
BasicString::Create(esapi),
BasicString::Create(esapi, std::move(decision_cache)),
File::Create(event_log_path, kFlushBufferTimeoutMS, kBufferBatchSizeBytes,
kMaxExpectedWriteSizeBytes));
case SNTEventLogTypeSyslog:
return std::make_unique<Logger>(BasicString::Create(esapi, false), Syslog::Create());
return std::make_unique<Logger>(BasicString::Create(esapi, std::move(decision_cache), false),
Syslog::Create());
case SNTEventLogTypeNull: return std::make_unique<Logger>(Empty::Create(), Null::Create());
case SNTEventLogTypeProtobuf:
LOGW(@"The EventLogType value protobuf is currently in beta. The protobuf schema is subject "
@"to change.");
return std::make_unique<Logger>(
Protobuf::Create(esapi),
Protobuf::Create(esapi, std::move(decision_cache)),
Spool::Create([spool_log_path UTF8String], spool_dir_size_threshold,
spool_file_size_threshold, spool_flush_timeout_ms));
default: LOGE(@"Invalid log type: %ld", log_type); return nullptr;
@@ -76,7 +80,7 @@ Logger::Logger(std::shared_ptr<serializers::Serializer> serializer,
std::shared_ptr<writers::Writer> writer)
: serializer_(std::move(serializer)), writer_(std::move(writer)) {}
void Logger::Log(std::shared_ptr<EnrichedMessage> msg) {
void Logger::Log(std::unique_ptr<EnrichedMessage> msg) {
writer_->Write(serializer_->SerializeMessage(std::move(msg)));
}
@@ -107,4 +111,8 @@ void Logger::LogFileAccess(
enriched_process, target, decision));
}
void Logger::Flush() {
writer_->Flush();
}
} // namespace santa::santad::logs::endpoint_security

View File

@@ -103,26 +103,26 @@ class MockWriter : public Null {
// Ensure that the factory method creates expected serializers/writers pairs
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
XCTAssertEqual(nullptr, Logger::Create(mockESApi, (SNTEventLogType)123, @"/tmp/temppy",
XCTAssertEqual(nullptr, Logger::Create(mockESApi, (SNTEventLogType)123, nil, @"/tmp/temppy",
@"/tmp/spool", 1, 1, 1));
LoggerPeer logger(
Logger::Create(mockESApi, SNTEventLogTypeFilelog, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
Logger::Create(mockESApi, SNTEventLogTypeFilelog, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<BasicString>(logger.Serializer()));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<File>(logger.Writer()));
logger = LoggerPeer(
Logger::Create(mockESApi, SNTEventLogTypeSyslog, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
Logger::Create(mockESApi, SNTEventLogTypeSyslog, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<BasicString>(logger.Serializer()));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Syslog>(logger.Writer()));
logger = LoggerPeer(
Logger::Create(mockESApi, SNTEventLogTypeNull, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
Logger::Create(mockESApi, SNTEventLogTypeNull, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Empty>(logger.Serializer()));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Null>(logger.Writer()));
logger = LoggerPeer(
Logger::Create(mockESApi, SNTEventLogTypeProtobuf, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
logger = LoggerPeer(Logger::Create(mockESApi, SNTEventLogTypeProtobuf, nil, @"/tmp/temppy",
@"/tmp/spool", 1, 1, 1));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Protobuf>(logger.Serializer()));
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Spool>(logger.Writer()));
}
@@ -136,16 +136,19 @@ class MockWriter : public Null {
es_message_t msg;
mockESApi->SetExpectationsRetainReleaseMessage();
auto enrichedMsg = std::make_shared<EnrichedMessage>(
EnrichedClose(Message(mockESApi, &msg),
EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt,
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)),
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)));
EXPECT_CALL(*mockSerializer, SerializeMessage(testing::A<const EnrichedClose &>())).Times(1);
EXPECT_CALL(*mockWriter, Write).Times(1);
{
auto enrichedMsg = std::make_unique<EnrichedMessage>(
EnrichedClose(Message(mockESApi, &msg),
EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt,
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)),
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)));
Logger(mockSerializer, mockWriter).Log(enrichedMsg);
EXPECT_CALL(*mockSerializer, SerializeMessage(testing::A<const EnrichedClose &>())).Times(1);
EXPECT_CALL(*mockWriter, Write).Times(1);
Logger(mockSerializer, mockWriter).Log(std::move(enrichedMsg));
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get());

View File

@@ -21,8 +21,10 @@
#include <sstream>
#include <vector>
#import "Source/common/SNTCachedDecision.h"
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Serializer.h"
#import "Source/santad/SNTDecisionCache.h"
namespace santa::santad::logs::endpoint_security::serializers {
@@ -30,18 +32,19 @@ class BasicString : public Serializer {
public:
static std::shared_ptr<BasicString> Create(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
bool prefix_time_name = true);
SNTDecisionCache *decision_cache, bool prefix_time_name = true);
BasicString(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
bool prefix_time_name);
SNTDecisionCache *decision_cache, bool prefix_time_name);
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedClose &) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExchange &) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExec &) override;
const santa::santad::event_providers::endpoint_security::EnrichedExec &,
SNTCachedDecision *) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExit &) override;
std::vector<uint8_t> SerializeMessage(

View File

@@ -29,7 +29,6 @@
#include <string>
#import "Source/common/SNTCachedDecision.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/SanitizableString.h"
@@ -92,11 +91,13 @@ std::string GetReasonString(SNTEventState event_state) {
case SNTEventStateAllowCertificate: return "CERT";
case SNTEventStateAllowScope: return "SCOPE";
case SNTEventStateAllowTeamID: return "TEAMID";
case SNTEventStateAllowSigningID: return "SIGNINGID";
case SNTEventStateAllowUnknown: return "UNKNOWN";
case SNTEventStateBlockBinary: return "BINARY";
case SNTEventStateBlockCertificate: return "CERT";
case SNTEventStateBlockScope: return "SCOPE";
case SNTEventStateBlockTeamID: return "TEAMID";
case SNTEventStateBlockSigningID: return "SIGNINGID";
case SNTEventStateBlockLongPath: return "LONG_PATH";
case SNTEventStateBlockUnknown: return "UNKNOWN";
default: return "NOTRUNNING";
@@ -177,12 +178,14 @@ static char *FormattedDateString(char *buf, size_t len) {
}
std::shared_ptr<BasicString> BasicString::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
SNTDecisionCache *decision_cache,
bool prefix_time_name) {
return std::make_shared<BasicString>(esapi, prefix_time_name);
return std::make_shared<BasicString>(esapi, decision_cache, prefix_time_name);
}
BasicString::BasicString(std::shared_ptr<EndpointSecurityAPI> esapi, bool prefix_time_name)
: esapi_(esapi), prefix_time_name_(prefix_time_name) {}
BasicString::BasicString(std::shared_ptr<EndpointSecurityAPI> esapi,
SNTDecisionCache *decision_cache, bool prefix_time_name)
: Serializer(std::move(decision_cache)), esapi_(esapi), prefix_time_name_(prefix_time_name) {}
std::string BasicString::CreateDefaultString(size_t reserved_size) {
std::string str;
@@ -241,13 +244,10 @@ std::vector<uint8_t> BasicString::SerializeMessage(const EnrichedExchange &msg)
return FinalizeString(str);
}
std::vector<uint8_t> BasicString::SerializeMessage(const EnrichedExec &msg) {
std::vector<uint8_t> BasicString::SerializeMessage(const EnrichedExec &msg, SNTCachedDecision *cd) {
const es_message_t &esm = msg.es_msg();
std::string str = CreateDefaultString(1024); // EXECs tend to be bigger, reserve more space.
SNTCachedDecision *cd =
[[SNTDecisionCache sharedCache] cachedDecisionForFile:esm.event.exec.target->executable->stat];
str.append("action=EXEC|decision=");
str.append(GetDecisionString(cd.decision));
str.append("|reason=");
@@ -291,7 +291,7 @@ std::vector<uint8_t> BasicString::SerializeMessage(const EnrichedExec &msg) {
msg.instigator().real_group());
str.append("|mode=");
str.append(GetModeString([[SNTConfigurator configurator] clientMode]));
str.append(GetModeString(cd.decisionClientMode));
str.append("|path=");
str.append(FilePath(esm.event.exec.target->executable).Sanitized());

View File

@@ -56,10 +56,10 @@ using santa::santad::logs::endpoint_security::serializers::GetModeString;
using santa::santad::logs::endpoint_security::serializers::GetReasonString;
std::string BasicStringSerializeMessage(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
es_message_t *esMsg, SNTDecisionCache *decisionCache) {
mockESApi->SetExpectationsRetainReleaseMessage();
std::shared_ptr<Serializer> bs = BasicString::Create(mockESApi, false);
std::shared_ptr<Serializer> bs = BasicString::Create(mockESApi, decisionCache, false);
std::vector<uint8_t> ret = bs->SerializeMessage(Enricher().Enrich(Message(mockESApi, esMsg)));
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
@@ -69,7 +69,7 @@ std::string BasicStringSerializeMessage(std::shared_ptr<MockEndpointSecurityAPI>
std::string BasicStringSerializeMessage(es_message_t *esMsg) {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
return BasicStringSerializeMessage(mockESApi, esMsg);
return BasicStringSerializeMessage(mockESApi, esMsg, nil);
}
@interface BasicStringTest : XCTestCase
@@ -94,10 +94,11 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
self.testCachedDecision.sha256 = @"1234_hash";
self.testCachedDecision.quarantineURL = @"google.com";
self.testCachedDecision.certSHA256 = @"5678_hash";
self.testCachedDecision.decisionClientMode = SNTClientModeLockdown;
self.mockDecisionCache = OCMClassMock([SNTDecisionCache class]);
OCMStub([self.mockDecisionCache sharedCache]).andReturn(self.mockDecisionCache);
OCMStub([self.mockDecisionCache cachedDecisionForFile:{}])
OCMStub([self.mockDecisionCache resetTimestampForCachedDecision:{}])
.ignoringNonObjectArgs()
.andReturn(self.testCachedDecision);
}
@@ -163,7 +164,7 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
.WillOnce(testing::Return(es_string_token_t{5, "-l\n-t"}))
.WillOnce(testing::Return(es_string_token_t{8, "-v\r--foo"}));
std::string got = BasicStringSerializeMessage(mockESApi, &esMsg);
std::string got = BasicStringSerializeMessage(mockESApi, &esMsg, self.mockDecisionCache);
std::string want =
"action=EXEC|decision=ALLOW|reason=BINARY|explain=extra!|sha256=1234_hash|"
"cert_sha256=5678_hash|cert_cn=|quarantine_url=google.com|pid=12|pidversion="
@@ -289,7 +290,7 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
mockESApi->SetExpectationsRetainReleaseMessage();
std::vector<uint8_t> ret =
BasicString::Create(nullptr, false)
BasicString::Create(nullptr, nil, false)
->SerializeFileAccess("v1.0", "pol_name", Message(mockESApi, &esMsg),
Enricher().Enrich(*esMsg.process), "file_target",
FileAccessPolicyDecision::kAllowedAuditOnly);
@@ -310,7 +311,7 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
mockESApi->SetExpectationsRetainReleaseMessage();
std::vector<uint8_t> ret = BasicString::Create(mockESApi, false)
std::vector<uint8_t> ret = BasicString::Create(mockESApi, nil, false)
->SerializeAllowlist(Message(mockESApi, &esMsg), "test_hash");
XCTAssertTrue(testing::Mock::VerifyAndClearExpectations(mockESApi.get()),
@@ -333,7 +334,8 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
se.fileBundlePath = @"file_bundle_path";
se.filePath = @"file_path";
std::vector<uint8_t> ret = BasicString::Create(nullptr, false)->SerializeBundleHashingEvent(se);
std::vector<uint8_t> ret =
BasicString::Create(nullptr, nil, false)->SerializeBundleHashingEvent(se);
std::string got(ret.begin(), ret.end());
std::string want = "action=BUNDLE|sha256=file_hash"
@@ -360,7 +362,7 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator enableMachineIDDecoration]).andReturn(NO);
std::vector<uint8_t> ret = BasicString::Create(nullptr, false)->SerializeDiskAppeared(props);
std::vector<uint8_t> ret = BasicString::Create(nullptr, nil, false)->SerializeDiskAppeared(props);
std::string got(ret.begin(), ret.end());
std::string want = "action=DISKAPPEAR|mount=path|volume=|bsdname=bsd|fs=apfs"
@@ -376,7 +378,8 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
@"DAMediaBSDName" : @"bsd",
};
std::vector<uint8_t> ret = BasicString::Create(nullptr, false)->SerializeDiskDisappeared(props);
std::vector<uint8_t> ret =
BasicString::Create(nullptr, nil, false)->SerializeDiskDisappeared(props);
std::string got(ret.begin(), ret.end());
std::string want = "action=DISKDISAPPEAR|mount=path|volume=|bsdname=bsd|machineid=my_id\n";
@@ -418,6 +421,7 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
{SNTEventStateBlockCertificate, "CERT"},
{SNTEventStateBlockScope, "SCOPE"},
{SNTEventStateBlockTeamID, "TEAMID"},
{SNTEventStateBlockSigningID, "SIGNINGID"},
{SNTEventStateBlockLongPath, "LONG_PATH"},
{SNTEventStateAllowUnknown, "UNKNOWN"},
{SNTEventStateAllowBinary, "BINARY"},
@@ -427,6 +431,7 @@ std::string BasicStringSerializeMessage(es_message_t *esMsg) {
{SNTEventStateAllowTransitive, "TRANSITIVE"},
{SNTEventStateAllowPendingTransitive, "PENDING_TRANSITIVE"},
{SNTEventStateAllowTeamID, "TEAMID"},
{SNTEventStateAllowSigningID, "SIGNINGID"},
};
for (const auto &kv : stateToReason) {

View File

@@ -20,6 +20,7 @@
#include <memory>
#include <vector>
#include "Source/common/SNTCachedDecision.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Serializer.h"
namespace santa::santad::logs::endpoint_security::serializers {
@@ -27,13 +28,15 @@ namespace santa::santad::logs::endpoint_security::serializers {
class Empty : public Serializer {
public:
static std::shared_ptr<Empty> Create();
Empty();
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedClose &) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExchange &) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExec &) override;
const santa::santad::event_providers::endpoint_security::EnrichedExec &,
SNTCachedDecision *) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExit &) override;
std::vector<uint8_t> SerializeMessage(

View File

@@ -31,6 +31,8 @@ std::shared_ptr<Empty> Empty::Create() {
return std::make_shared<Empty>();
}
Empty::Empty() : Serializer(nil) {}
std::vector<uint8_t> Empty::SerializeMessage(const EnrichedClose &msg) {
return {};
}
@@ -39,7 +41,7 @@ std::vector<uint8_t> Empty::SerializeMessage(const EnrichedExchange &msg) {
return {};
}
std::vector<uint8_t> Empty::SerializeMessage(const EnrichedExec &msg) {
std::vector<uint8_t> Empty::SerializeMessage(const EnrichedExec &msg, SNTCachedDecision *cd) {
return {};
}

View File

@@ -37,7 +37,7 @@ namespace es = santa::santad::event_providers::endpoint_security;
int fake;
XCTAssertEqual(e->SerializeMessage(*(es::EnrichedClose *)&fake).size(), 0);
XCTAssertEqual(e->SerializeMessage(*(es::EnrichedExchange *)&fake).size(), 0);
XCTAssertEqual(e->SerializeMessage(*(es::EnrichedExec *)&fake).size(), 0);
XCTAssertEqual(e->SerializeMessage(*(es::EnrichedExec *)&fake, nil).size(), 0);
XCTAssertEqual(e->SerializeMessage(*(es::EnrichedExit *)&fake).size(), 0);
XCTAssertEqual(e->SerializeMessage(*(es::EnrichedFork *)&fake).size(), 0);
XCTAssertEqual(e->SerializeMessage(*(es::EnrichedLink *)&fake).size(), 0);

View File

@@ -21,26 +21,31 @@
#include <memory>
#include <vector>
#import "Source/common/SNTCachedDecision.h"
#include "Source/common/santa_proto_include_wrapper.h"
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Serializer.h"
#import "Source/santad/SNTDecisionCache.h"
namespace santa::santad::logs::endpoint_security::serializers {
class Protobuf : public Serializer {
public:
static std::shared_ptr<Protobuf> Create(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi);
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
SNTDecisionCache *decision_cache);
Protobuf(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi);
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
SNTDecisionCache *decision_cache);
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedClose &) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExchange &) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExec &) override;
const santa::santad::event_providers::endpoint_security::EnrichedExec &,
SNTCachedDecision *) override;
std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExit &) override;
std::vector<uint8_t> SerializeMessage(

View File

@@ -23,13 +23,14 @@
#include <sys/wait.h>
#include <time.h>
#include <functional>
#include <optional>
#include <string_view>
#import "Source/common/SNTCachedDecision.h"
#import "Source/common/SNTConfigurator.h"
#include "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/String.h"
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h"
#import "Source/santad/SNTDecisionCache.h"
@@ -38,6 +39,7 @@
using google::protobuf::Arena;
using google::protobuf::Timestamp;
using santa::common::NSStringToUTF8StringView;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::EnrichedClose;
using santa::santad::event_providers::endpoint_security::EnrichedEventType;
@@ -63,11 +65,13 @@ namespace pbv1 = ::santa::pb::v1;
namespace santa::santad::logs::endpoint_security::serializers {
std::shared_ptr<Protobuf> Protobuf::Create(std::shared_ptr<EndpointSecurityAPI> esapi) {
return std::make_shared<Protobuf>(esapi);
std::shared_ptr<Protobuf> Protobuf::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
SNTDecisionCache *decision_cache) {
return std::make_shared<Protobuf>(esapi, std::move(decision_cache));
}
Protobuf::Protobuf(std::shared_ptr<EndpointSecurityAPI> esapi) : esapi_(esapi) {}
Protobuf::Protobuf(std::shared_ptr<EndpointSecurityAPI> esapi, SNTDecisionCache *decision_cache)
: Serializer(std::move(decision_cache)), esapi_(esapi) {}
static inline void EncodeTimestamp(Timestamp *timestamp, struct timespec ts) {
timestamp->set_seconds(ts.tv_sec);
@@ -94,15 +98,15 @@ static inline void EncodePath(std::string *buf, const es_file_t *es_file) {
buf->append(std::string_view(es_file->path.data, es_file->path.length));
}
static inline void EncodeString(std::string *buf, NSString *value) {
static inline void EncodeString(std::function<std::string *()> lazy_f, NSString *value) {
if (value) {
buf->append(std::string_view([value UTF8String], [value length]));
lazy_f()->append(NSStringToUTF8StringView(value));
}
}
static inline void EncodeString(std::string *buf, std::string_view value) {
static inline void EncodeString(std::function<std::string *()> lazy_f, std::string_view value) {
if (value.length() > 0) {
buf->append(std::string_view(value.data(), value.length()));
lazy_f()->append(value);
}
}
@@ -125,7 +129,7 @@ static inline void EncodeGroupInfo(::pbv1::GroupInfo *pb_group_info, gid_t gid,
static inline void EncodeHash(::pbv1::Hash *pb_hash, NSString *sha256) {
if (sha256) {
pb_hash->set_type(::pbv1::Hash::HASH_ALGO_SHA256);
pb_hash->set_hash([sha256 UTF8String], [sha256 length]);
EncodeString([pb_hash] { return pb_hash->mutable_hash(); }, sha256);
}
}
@@ -162,7 +166,7 @@ static inline void EncodeFileInfo(::pbv1::FileInfo *pb_file, const es_file_t *es
static inline void EncodeFileInfoLight(::pbv1::FileInfoLight *pb_file, std::string_view path,
bool truncated) {
EncodeString(pb_file->mutable_path(), path);
EncodeString([pb_file] { return pb_file->mutable_path(); }, path);
pb_file->set_truncated(truncated);
}
@@ -262,9 +266,7 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
EncodeHash(pb_cert_info->mutable_hash(), cert_hash);
}
if (common_name) {
pb_cert_info->set_common_name([common_name UTF8String], [common_name length]);
}
EncodeString([pb_cert_info] { return pb_cert_info->mutable_common_name(); }, common_name);
}
::pbv1::Execution::Decision GetDecisionEnum(SNTEventState event_state) {
@@ -286,11 +288,13 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
case SNTEventStateAllowCertificate: return ::pbv1::Execution::REASON_CERT;
case SNTEventStateAllowScope: return ::pbv1::Execution::REASON_SCOPE;
case SNTEventStateAllowTeamID: return ::pbv1::Execution::REASON_TEAM_ID;
case SNTEventStateAllowSigningID: return ::pbv1::Execution::REASON_SIGNING_ID;
case SNTEventStateAllowUnknown: return ::pbv1::Execution::REASON_UNKNOWN;
case SNTEventStateBlockBinary: return ::pbv1::Execution::REASON_BINARY;
case SNTEventStateBlockCertificate: return ::pbv1::Execution::REASON_CERT;
case SNTEventStateBlockScope: return ::pbv1::Execution::REASON_SCOPE;
case SNTEventStateBlockTeamID: return ::pbv1::Execution::REASON_TEAM_ID;
case SNTEventStateBlockSigningID: return ::pbv1::Execution::REASON_SIGNING_ID;
case SNTEventStateBlockLongPath: return ::pbv1::Execution::REASON_LONG_PATH;
case SNTEventStateBlockUnknown: return ::pbv1::Execution::REASON_UNKNOWN;
default: return ::pbv1::Execution::REASON_NOT_RUNNING;
@@ -356,7 +360,7 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
::pbv1::SantaMessage *santa_msg = Arena::CreateMessage<::pbv1::SantaMessage>(arena);
if (EnabledMachineID()) {
EncodeString(santa_msg->mutable_machine_id(), MachineID());
EncodeString([santa_msg] { return santa_msg->mutable_machine_id(); }, MachineID());
}
EncodeTimestamp(santa_msg->mutable_event_time(), event_time);
EncodeTimestamp(santa_msg->mutable_processed_time(), processed_time);
@@ -384,7 +388,7 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
std::vector<uint8_t> Protobuf::FinalizeProto(::pbv1::SantaMessage *santa_msg) {
std::vector<uint8_t> vec(santa_msg->ByteSizeLong());
santa_msg->SerializeToArray(vec.data(), (int)vec.capacity());
santa_msg->SerializeWithCachedSizesToArray(vec.data());
return vec;
}
@@ -418,13 +422,10 @@ std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExchange &msg) {
return FinalizeProto(santa_msg);
}
std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg) {
std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg, SNTCachedDecision *cd) {
Arena arena;
::pbv1::SantaMessage *santa_msg = CreateDefaultProto(&arena, msg);
SNTCachedDecision *cd = [[SNTDecisionCache sharedCache]
cachedDecisionForFile:msg.es_msg().event.exec.target->executable->stat];
GetDecisionEnum(cd.decision);
::pbv1::Execution *pb_exec = santa_msg->mutable_execution();
@@ -444,28 +445,37 @@ std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg) {
}
uint32_t arg_count = esapi_->ExecArgCount(&msg.es_msg().event.exec);
for (uint32_t i = 0; i < arg_count; i++) {
es_string_token_t tok = esapi_->ExecArg(&msg.es_msg().event.exec, i);
pb_exec->add_args(tok.data, tok.length);
if (arg_count > 0) {
pb_exec->mutable_args()->Reserve(arg_count);
for (uint32_t i = 0; i < arg_count; i++) {
es_string_token_t tok = esapi_->ExecArg(&msg.es_msg().event.exec, i);
pb_exec->add_args(tok.data, tok.length);
}
}
uint32_t env_count = esapi_->ExecEnvCount(&msg.es_msg().event.exec);
for (uint32_t i = 0; i < env_count; i++) {
es_string_token_t tok = esapi_->ExecEnv(&msg.es_msg().event.exec, i);
pb_exec->add_envs(tok.data, tok.length);
if (env_count > 0) {
pb_exec->mutable_envs()->Reserve(env_count);
for (uint32_t i = 0; i < env_count; i++) {
es_string_token_t tok = esapi_->ExecEnv(&msg.es_msg().event.exec, i);
pb_exec->add_envs(tok.data, tok.length);
}
}
if (msg.es_msg().version >= 4) {
int32_t max_fd = -1;
uint32_t fd_count = esapi_->ExecFDCount(&msg.es_msg().event.exec);
for (uint32_t i = 0; i < fd_count; i++) {
const es_fd_t *fd = esapi_->ExecFD(&msg.es_msg().event.exec, i);
max_fd = std::max(max_fd, fd->fd);
::pbv1::FileDescriptor *pb_fd = pb_exec->add_fds();
pb_fd->set_fd(fd->fd);
pb_fd->set_fd_type(GetFileDescriptorType(fd->fdtype));
if (fd->fdtype == PROX_FDTYPE_PIPE) {
pb_fd->set_pipe_id(fd->pipe.pipe_id);
if (fd_count > 0) {
pb_exec->mutable_fds()->Reserve(fd_count);
for (uint32_t i = 0; i < fd_count; i++) {
const es_fd_t *fd = esapi_->ExecFD(&msg.es_msg().event.exec, i);
max_fd = std::max(max_fd, fd->fd);
::pbv1::FileDescriptor *pb_fd = pb_exec->add_fds();
pb_fd->set_fd(fd->fd);
pb_fd->set_fd_type(GetFileDescriptorType(fd->fdtype));
if (fd->fdtype == PROX_FDTYPE_PIPE) {
pb_fd->set_pipe_id(fd->pipe.pipe_id);
}
}
}
@@ -476,24 +486,17 @@ std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg) {
pb_exec->set_decision(GetDecisionEnum(cd.decision));
pb_exec->set_reason(GetReasonEnum(cd.decision));
pb_exec->set_mode(GetModeEnum([[SNTConfigurator configurator] clientMode]));
pb_exec->set_mode(GetModeEnum(cd.decisionClientMode));
if (cd.certSHA256 || cd.certCommonName) {
EncodeCertificateInfo(pb_exec->mutable_certificate_info(), cd.certSHA256, cd.certCommonName);
}
if (cd.decisionExtra) {
pb_exec->set_explain([cd.decisionExtra UTF8String], [cd.decisionExtra length]);
}
if (cd.quarantineURL) {
pb_exec->set_quarantine_url([cd.quarantineURL UTF8String], [cd.quarantineURL length]);
}
EncodeString([pb_exec] { return pb_exec->mutable_explain(); }, cd.decisionExtra);
EncodeString([pb_exec] { return pb_exec->mutable_quarantine_url(); }, cd.quarantineURL);
NSString *orig_path = Utilities::OriginalPathForTranslocation(msg.es_msg().event.exec.target);
if (orig_path) {
pb_exec->set_original_path([orig_path UTF8String], [orig_path length]);
}
EncodeString([pb_exec] { return pb_exec->mutable_original_path(); }, orig_path);
return FinalizeProto(santa_msg);
}
@@ -585,8 +588,9 @@ std::vector<uint8_t> Protobuf::SerializeFileAccess(const std::string &policy_ver
EncodeProcessInfo(file_access->mutable_instigator(), msg->version, msg->process,
enriched_process);
EncodeFileInfoLight(file_access->mutable_target(), target, false);
EncodeString(file_access->mutable_policy_version(), policy_version);
EncodeString(file_access->mutable_policy_name(), policy_name);
EncodeString([file_access] { return file_access->mutable_policy_version(); }, policy_version);
EncodeString([file_access] { return file_access->mutable_policy_name(); }, policy_name);
file_access->set_access_type(GetAccessType(msg->event_type));
file_access->set_policy_decision(GetPolicyDecision(decision));
@@ -620,10 +624,12 @@ std::vector<uint8_t> Protobuf::SerializeBundleHashingEvent(SNTStoredEvent *event
EncodeHash(pb_bundle->mutable_file_hash(), event.fileSHA256);
EncodeHash(pb_bundle->mutable_bundle_hash(), event.fileBundleHash);
pb_bundle->set_bundle_name([NonNull(event.fileBundleName) UTF8String]);
pb_bundle->set_bundle_id([NonNull(event.fileBundleID) UTF8String]);
pb_bundle->set_bundle_path([NonNull(event.fileBundlePath) UTF8String]);
pb_bundle->set_path([NonNull(event.filePath) UTF8String]);
EncodeString([pb_bundle] { return pb_bundle->mutable_bundle_name(); },
NonNull(event.fileBundleName));
EncodeString([pb_bundle] { return pb_bundle->mutable_bundle_id(); }, NonNull(event.fileBundleID));
EncodeString([pb_bundle] { return pb_bundle->mutable_bundle_path(); },
NonNull(event.fileBundlePath));
EncodeString([pb_bundle] { return pb_bundle->mutable_path(); }, NonNull(event.filePath));
return FinalizeProto(santa_msg);
}
@@ -643,14 +649,14 @@ static void EncodeDisk(::pbv1::Disk *pb_disk, ::pbv1::Disk_Action action, NSDict
stringWithFormat:@"%@ %@", NonNull(props[@"DADeviceVendor"]), NonNull(props[@"DADeviceModel"])];
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
EncodeString(pb_disk->mutable_mount(), [props[@"DAVolumePath"] path]);
EncodeString(pb_disk->mutable_volume(), props[@"DAVolumeName"]);
EncodeString(pb_disk->mutable_bsd_name(), props[@"DAMediaBSDName"]);
EncodeString(pb_disk->mutable_fs(), props[@"DAVolumeKind"]);
EncodeString(pb_disk->mutable_model(), model);
EncodeString(pb_disk->mutable_serial(), serial);
EncodeString(pb_disk->mutable_bus(), props[@"DADeviceProtocol"]);
EncodeString(pb_disk->mutable_dmg_path(), dmg_path);
EncodeString([pb_disk] { return pb_disk->mutable_mount(); }, [props[@"DAVolumePath"] path]);
EncodeString([pb_disk] { return pb_disk->mutable_volume(); }, props[@"DAVolumeName"]);
EncodeString([pb_disk] { return pb_disk->mutable_bsd_name(); }, props[@"DAMediaBSDName"]);
EncodeString([pb_disk] { return pb_disk->mutable_fs(); }, props[@"DAVolumeKind"]);
EncodeString([pb_disk] { return pb_disk->mutable_model(); }, model);
EncodeString([pb_disk] { return pb_disk->mutable_serial(); }, serial);
EncodeString([pb_disk] { return pb_disk->mutable_bus(); }, props[@"DADeviceProtocol"]);
EncodeString([pb_disk] { return pb_disk->mutable_dmg_path(); }, dmg_path);
if (props[@"DAAppearanceTime"]) {
// Note: `DAAppearanceTime` is set via `CFAbsoluteTimeGetCurrent`, which uses the defined

View File

@@ -128,12 +128,6 @@ bool CompareTime(const Timestamp &timestamp, struct timespec ts) {
return timestamp.seconds() == ts.tv_sec && timestamp.nanos() == ts.tv_nsec;
}
void CheckSantaMessage(const ::pbv1::SantaMessage &santaMsg, const es_message_t &esMsg,
struct timespec enrichmentTime) {
XCTAssertTrue(CompareTime(santaMsg.processed_time(), enrichmentTime));
XCTAssertTrue(CompareTime(santaMsg.event_time(), esMsg.time));
}
const google::protobuf::Message &SantaMessageEvent(const ::pbv1::SantaMessage &santaMsg) {
switch (santaMsg.event_case()) {
case ::pbv1::SantaMessage::kExecution: return santaMsg.execution();
@@ -167,23 +161,10 @@ std::string ConvertMessageToJsonString(const ::pbv1::SantaMessage &santaMsg) {
return json;
}
void CheckProto(const ::pbv1::SantaMessage &santaMsg,
std::shared_ptr<EnrichedMessage> enrichedMsg) {
return std::visit(
[santaMsg](const EnrichedEventType &enrichedEvent) {
CheckSantaMessage(santaMsg, enrichedEvent.es_msg(), enrichedEvent.enrichment_time());
NSString *wantData = LoadTestJson(EventTypeToFilename(enrichedEvent.es_msg().event_type),
enrichedEvent.es_msg().version);
std::string got = ConvertMessageToJsonString(santaMsg);
XCTAssertEqualObjects([NSString stringWithUTF8String:got.c_str()], wantData);
},
enrichedMsg->GetEnrichedMessage());
}
void SerializeAndCheck(es_event_type_t eventType,
void (^messageSetup)(std::shared_ptr<MockEndpointSecurityAPI>,
es_message_t *)) {
es_message_t *),
SNTDecisionCache *decisionCache) {
std::shared_ptr<MockEndpointSecurityAPI> mockESApi = std::make_shared<MockEndpointSecurityAPI>();
for (uint32_t cur_version = 1; cur_version <= MaxSupportedESMessageVersionForCurrentOS();
@@ -204,16 +185,33 @@ void SerializeAndCheck(es_event_type_t eventType,
messageSetup(mockESApi, &esMsg);
std::shared_ptr<Serializer> bs = Protobuf::Create(mockESApi);
std::shared_ptr<EnrichedMessage> enrichedMsg = Enricher().Enrich(Message(mockESApi, &esMsg));
std::shared_ptr<Serializer> bs = Protobuf::Create(mockESApi, decisionCache);
std::unique_ptr<EnrichedMessage> enrichedMsg = Enricher().Enrich(Message(mockESApi, &esMsg));
std::vector<uint8_t> vec = bs->SerializeMessage(enrichedMsg);
// Copy some values we need to check later before the object is moved out of this funciton
struct timespec enrichmentTime;
struct timespec msgTime;
NSString *wantData = std::visit(
[&msgTime, &enrichmentTime](const EnrichedEventType &enrichedEvent) {
msgTime = enrichedEvent.es_msg().time;
enrichmentTime = enrichedEvent.enrichment_time();
return LoadTestJson(EventTypeToFilename(enrichedEvent.es_msg().event_type),
enrichedEvent.es_msg().version);
},
enrichedMsg->GetEnrichedMessage());
std::vector<uint8_t> vec = bs->SerializeMessage(std::move(enrichedMsg));
std::string protoStr(vec.begin(), vec.end());
::pbv1::SantaMessage santaMsg;
XCTAssertTrue(santaMsg.ParseFromString(protoStr));
CheckProto(santaMsg, enrichedMsg);
std::string gotData = ConvertMessageToJsonString(santaMsg);
XCTAssertTrue(CompareTime(santaMsg.processed_time(), enrichmentTime));
XCTAssertTrue(CompareTime(santaMsg.event_time(), msgTime));
XCTAssertEqualObjects([NSString stringWithUTF8String:gotData.c_str()], wantData);
}
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
@@ -226,7 +224,7 @@ void SerializeAndCheckNonESEvents(
const Message &msg)) {
std::shared_ptr<MockEndpointSecurityAPI> mockESApi = std::make_shared<MockEndpointSecurityAPI>();
mockESApi->SetExpectationsRetainReleaseMessage();
std::shared_ptr<Serializer> bs = Protobuf::Create(mockESApi);
std::shared_ptr<Serializer> bs = Protobuf::Create(mockESApi, nil);
for (uint32_t cur_version = 1; cur_version <= MaxSupportedESMessageVersionForCurrentOS();
cur_version++) {
@@ -280,10 +278,11 @@ void SerializeAndCheckNonESEvents(
self.testCachedDecision.sha256 = @"1234_file_hash";
self.testCachedDecision.quarantineURL = @"google.com";
self.testCachedDecision.certSHA256 = @"5678_cert_hash";
self.testCachedDecision.decisionClientMode = SNTClientModeLockdown;
self.mockDecisionCache = OCMClassMock([SNTDecisionCache class]);
OCMStub([self.mockDecisionCache sharedCache]).andReturn(self.mockDecisionCache);
OCMStub([self.mockDecisionCache cachedDecisionForFile:{}])
OCMStub([self.mockDecisionCache resetTimestampForCachedDecision:{}])
.ignoringNonObjectArgs()
.andReturn(self.testCachedDecision);
}
@@ -293,25 +292,33 @@ void SerializeAndCheckNonESEvents(
[self.mockDecisionCache stopMocking];
}
- (void)serializeAndCheckEvent:(es_event_type_t)eventType
messageSetup:(void (^)(std::shared_ptr<MockEndpointSecurityAPI>,
es_message_t *))messageSetup {
SerializeAndCheck(eventType, messageSetup, self.mockDecisionCache);
}
- (void)testSerializeMessageClose {
__block es_file_t file = MakeESFile("close_file", MakeStat(300));
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_CLOSE,
^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi, es_message_t *esMsg) {
esMsg->event.close.modified = true;
esMsg->event.close.target = &file;
});
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_CLOSE
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.close.modified = true;
esMsg->event.close.target = &file;
}];
}
- (void)testSerializeMessageExchange {
__block es_file_t file1 = MakeESFile("exchange_file_1", MakeStat(300));
__block es_file_t file2 = MakeESFile("exchange_file_1", MakeStat(400));
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA,
^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi, es_message_t *esMsg) {
esMsg->event.exchangedata.file1 = &file1;
esMsg->event.exchangedata.file2 = &file2;
});
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.exchangedata.file1 = &file1;
esMsg->event.exchangedata.file2 = &file2;
}];
}
- (void)testGetDecisionEnum {
@@ -335,7 +342,7 @@ void SerializeAndCheckNonESEvents(
};
for (const auto &kv : stateToDecision) {
XCTAssertEqual(GetDecisionEnum(kv.first), kv.second, @"Bad decision for state: %ld", kv.first);
XCTAssertEqual(GetDecisionEnum(kv.first), kv.second, @"Bad decision for state: %llu", kv.first);
}
}
@@ -348,6 +355,7 @@ void SerializeAndCheckNonESEvents(
{SNTEventStateBlockCertificate, ::pbv1::Execution::REASON_CERT},
{SNTEventStateBlockScope, ::pbv1::Execution::REASON_SCOPE},
{SNTEventStateBlockTeamID, ::pbv1::Execution::REASON_TEAM_ID},
{SNTEventStateBlockSigningID, ::pbv1::Execution::REASON_SIGNING_ID},
{SNTEventStateBlockLongPath, ::pbv1::Execution::REASON_LONG_PATH},
{SNTEventStateAllowUnknown, ::pbv1::Execution::REASON_UNKNOWN},
{SNTEventStateAllowBinary, ::pbv1::Execution::REASON_BINARY},
@@ -357,10 +365,11 @@ void SerializeAndCheckNonESEvents(
{SNTEventStateAllowTransitive, ::pbv1::Execution::REASON_TRANSITIVE},
{SNTEventStateAllowPendingTransitive, ::pbv1::Execution::REASON_PENDING_TRANSITIVE},
{SNTEventStateAllowTeamID, ::pbv1::Execution::REASON_TEAM_ID},
{SNTEventStateAllowSigningID, ::pbv1::Execution::REASON_SIGNING_ID},
};
for (const auto &kv : stateToReason) {
XCTAssertEqual(GetReasonEnum(kv.first), kv.second, @"Bad reason for state: %ld", kv.first);
XCTAssertEqual(GetReasonEnum(kv.first), kv.second, @"Bad reason for state: %llu", kv.first);
}
}
@@ -373,7 +382,7 @@ void SerializeAndCheckNonESEvents(
};
for (const auto &kv : clientModeToExecMode) {
XCTAssertEqual(GetModeEnum(kv.first), kv.second, @"Bad mode for state: %ld", kv.first);
XCTAssertEqual(GetModeEnum(kv.first), kv.second, @"Bad mode for client mode: %ld", kv.first);
}
}
@@ -413,45 +422,48 @@ void SerializeAndCheckNonESEvents(
procTarget.signing_id = MakeESStringToken("my_signing_id");
procTarget.team_id = MakeESStringToken("my_team_id");
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_EXEC, ^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.exec.target = &procTarget;
esMsg->event.exec.cwd = &fileCwd;
esMsg->event.exec.script = &fileScript;
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_EXEC
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.exec.target = &procTarget;
esMsg->event.exec.cwd = &fileCwd;
esMsg->event.exec.script = &fileScript;
// For version 5, simulate a "truncated" set of FDs
if (esMsg->version == 5) {
esMsg->event.exec.last_fd = 123;
} else {
esMsg->event.exec.last_fd = 3;
}
// For version 5, simulate a "truncated" set of FDs
if (esMsg->version == 5) {
esMsg->event.exec.last_fd = 123;
} else {
esMsg->event.exec.last_fd = 3;
}
EXPECT_CALL(*mockESApi, ExecArgCount).WillOnce(testing::Return(3));
EXPECT_CALL(*mockESApi, ExecArg)
.WillOnce(testing::Return(MakeESStringToken("exec_path")))
.WillOnce(testing::Return(MakeESStringToken("-l")))
.WillOnce(testing::Return(MakeESStringToken("--foo")));
EXPECT_CALL(*mockESApi, ExecArgCount).WillOnce(testing::Return(3));
EXPECT_CALL(*mockESApi, ExecArg)
.WillOnce(testing::Return(MakeESStringToken("exec_path")))
.WillOnce(testing::Return(MakeESStringToken("-l")))
.WillOnce(testing::Return(MakeESStringToken("--foo")));
EXPECT_CALL(*mockESApi, ExecEnvCount).WillOnce(testing::Return(2));
EXPECT_CALL(*mockESApi, ExecEnv)
.WillOnce(testing::Return(MakeESStringToken("ENV_PATH=/path/to/bin:/and/another")))
.WillOnce(testing::Return(MakeESStringToken("DEBUG=1")));
EXPECT_CALL(*mockESApi, ExecEnvCount).WillOnce(testing::Return(2));
EXPECT_CALL(*mockESApi, ExecEnv)
.WillOnce(
testing::Return(MakeESStringToken("ENV_PATH=/path/to/bin:/and/another")))
.WillOnce(testing::Return(MakeESStringToken("DEBUG=1")));
if (esMsg->version >= 4) {
EXPECT_CALL(*mockESApi, ExecFDCount).WillOnce(testing::Return(3));
EXPECT_CALL(*mockESApi, ExecFD)
.WillOnce(testing::Return(&fd1))
.WillOnce(testing::Return(&fd2))
.WillOnce(testing::Return(&fd3));
}
});
if (esMsg->version >= 4) {
EXPECT_CALL(*mockESApi, ExecFDCount).WillOnce(testing::Return(3));
EXPECT_CALL(*mockESApi, ExecFD)
.WillOnce(testing::Return(&fd1))
.WillOnce(testing::Return(&fd2))
.WillOnce(testing::Return(&fd3));
}
}];
}
- (void)testSerializeMessageExit {
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_EXIT,
^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi, es_message_t *esMsg) {
esMsg->event.exit.stat = W_EXITCODE(1, 0);
});
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_EXIT
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.exit.stat = W_EXITCODE(1, 0);
}];
}
- (void)testEncodeExitStatus {
@@ -484,10 +496,11 @@ void SerializeAndCheckNonESEvents(
MakeESProcess(&procFileChild, MakeAuditToken(12, 34), MakeAuditToken(56, 78));
procChild.tty = &ttyFileChild;
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_FORK,
^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi, es_message_t *esMsg) {
esMsg->event.fork.child = &procChild;
});
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_FORK
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.fork.child = &procChild;
}];
}
- (void)testSerializeMessageLink {
@@ -495,12 +508,13 @@ void SerializeAndCheckNonESEvents(
__block es_file_t fileTargetDir = MakeESFile("target_dir");
es_string_token_t targetTok = MakeESStringToken("target_file");
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_LINK,
^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi, es_message_t *esMsg) {
esMsg->event.link.source = &fileSource;
esMsg->event.link.target_dir = &fileTargetDir;
esMsg->event.link.target_filename = targetTok;
});
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_LINK
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.link.source = &fileSource;
esMsg->event.link.target_dir = &fileTargetDir;
esMsg->event.link.target_filename = targetTok;
}];
}
- (void)testSerializeMessageRename {
@@ -508,30 +522,32 @@ void SerializeAndCheckNonESEvents(
__block es_file_t fileTargetDir = MakeESFile("target_dir");
es_string_token_t targetTok = MakeESStringToken("target_file");
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_RENAME,
^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi, es_message_t *esMsg) {
esMsg->event.rename.source = &fileSource;
// Test new and existing destination types
if (esMsg->version == 4) {
esMsg->event.rename.destination.existing_file = &fileTargetDir;
esMsg->event.rename.destination_type = ES_DESTINATION_TYPE_EXISTING_FILE;
} else {
esMsg->event.rename.destination.new_path.dir = &fileTargetDir;
esMsg->event.rename.destination.new_path.filename = targetTok;
esMsg->event.rename.destination_type = ES_DESTINATION_TYPE_NEW_PATH;
}
});
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_RENAME
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.rename.source = &fileSource;
// Test new and existing destination types
if (esMsg->version == 4) {
esMsg->event.rename.destination.existing_file = &fileTargetDir;
esMsg->event.rename.destination_type = ES_DESTINATION_TYPE_EXISTING_FILE;
} else {
esMsg->event.rename.destination.new_path.dir = &fileTargetDir;
esMsg->event.rename.destination.new_path.filename = targetTok;
esMsg->event.rename.destination_type = ES_DESTINATION_TYPE_NEW_PATH;
}
}];
}
- (void)testSerializeMessageUnlink {
__block es_file_t fileTarget = MakeESFile("unlink_file", MakeStat(300));
__block es_file_t fileTargetParent = MakeESFile("unlink_file_parent", MakeStat(400));
SerializeAndCheck(ES_EVENT_TYPE_NOTIFY_UNLINK,
^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi, es_message_t *esMsg) {
esMsg->event.unlink.target = &fileTarget;
esMsg->event.unlink.parent_dir = &fileTargetParent;
});
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_UNLINK
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
es_message_t *esMsg) {
esMsg->event.unlink.target = &fileTarget;
esMsg->event.unlink.parent_dir = &fileTargetParent;
}];
}
- (void)testGetAccessType {
@@ -608,7 +624,7 @@ void SerializeAndCheckNonESEvents(
se.fileBundlePath = @"file_bundle_path";
se.filePath = @"file_path";
std::vector<uint8_t> vec = Protobuf::Create(nullptr)->SerializeBundleHashingEvent(se);
std::vector<uint8_t> vec = Protobuf::Create(nullptr, nil)->SerializeBundleHashingEvent(se);
std::string protoStr(vec.begin(), vec.end());
::pbv1::SantaMessage santaMsg;
@@ -643,7 +659,7 @@ void SerializeAndCheckNonESEvents(
@"DADeviceProtocol" : @"usb",
};
std::vector<uint8_t> vec = Protobuf::Create(nullptr)->SerializeDiskAppeared(props);
std::vector<uint8_t> vec = Protobuf::Create(nullptr, nil)->SerializeDiskAppeared(props);
std::string protoStr(vec.begin(), vec.end());
::pbv1::SantaMessage santaMsg;
@@ -680,7 +696,7 @@ void SerializeAndCheckNonESEvents(
@"DADeviceProtocol" : @"usb",
};
std::vector<uint8_t> vec = Protobuf::Create(nullptr)->SerializeDiskDisappeared(props);
std::vector<uint8_t> vec = Protobuf::Create(nullptr, nil)->SerializeDiskDisappeared(props);
std::string protoStr(vec.begin(), vec.end());
::pbv1::SantaMessage santaMsg;

View File

@@ -51,8 +51,7 @@ class SanitizableString {
friend std::ostream &operator<<(std::ostream &ss, const SanitizableString &sani_string);
private:
const char *data_;
size_t length_;
std::string_view data_;
mutable bool sanitized_ = false;
mutable std::optional<std::string> sanitized_string_;
};

View File

@@ -14,37 +14,35 @@
#include "Source/santad/Logs/EndpointSecurity/Serializers/SanitizableString.h"
#include "Source/common/String.h"
using santa::common::NSStringToUTF8StringView;
namespace santa::santad::logs::endpoint_security::serializers {
SanitizableString::SanitizableString(const es_file_t *file)
: data_(file->path.data), length_(file->path.length) {}
: data_(file->path.data, file->path.length) {}
SanitizableString::SanitizableString(const es_string_token_t &tok)
: data_(tok.data), length_(tok.length) {}
SanitizableString::SanitizableString(const es_string_token_t &tok) : data_(tok.data, tok.length) {}
SanitizableString::SanitizableString(NSString *str)
: data_([str UTF8String]), length_([str length]) {}
SanitizableString::SanitizableString(NSString *str) : data_(NSStringToUTF8StringView(str)) {}
SanitizableString::SanitizableString(const char *str, size_t len) : data_(str), length_(len) {}
SanitizableString::SanitizableString(const char *str, size_t len) : data_(str, len) {}
std::string_view SanitizableString::String() const {
return std::string_view(data_, length_);
return data_;
}
std::string_view SanitizableString::Sanitized() const {
if (!sanitized_) {
sanitized_ = true;
sanitized_string_ = SanitizeString(data_, length_);
sanitized_string_ = SanitizeString(data_.data(), data_.length());
}
if (sanitized_string_.has_value()) {
return sanitized_string_.value();
} else {
if (data_) {
return std::string_view(data_, length_);
} else {
return "";
}
return data_;
}
}

View File

@@ -15,13 +15,16 @@
#ifndef SANTA__SANTAD__LOGS_ENDPOINTSECURITY_SERIALIZERS_SERIALIZER_H
#define SANTA__SANTAD__LOGS_ENDPOINTSECURITY_SERIALIZERS_SERIALIZER_H
#import <Foundation/Foundation.h>
#include <functional>
#include <memory>
#include <vector>
#import <Foundation/Foundation.h>
#import "Source/common/SNTCachedDecision.h"
#import "Source/common/SNTCommonEnums.h"
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
#import "Source/santad/SNTDecisionCache.h"
@class SNTStoredEvent;
@@ -29,11 +32,11 @@ namespace santa::santad::logs::endpoint_security::serializers {
class Serializer {
public:
Serializer();
Serializer(SNTDecisionCache *decision_cache);
virtual ~Serializer() = default;
std::vector<uint8_t> SerializeMessage(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage> msg) {
std::unique_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage> msg) {
return std::visit([this](const auto &arg) { return this->SerializeMessageTemplate(arg); },
msg->GetEnrichedMessage());
}
@@ -46,7 +49,8 @@ class Serializer {
virtual std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExchange &) = 0;
virtual std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExec &) = 0;
const santa::santad::event_providers::endpoint_security::EnrichedExec &,
SNTCachedDecision *cd) = 0;
virtual std::vector<uint8_t> SerializeMessage(
const santa::santad::event_providers::endpoint_security::EnrichedExit &) = 0;
virtual std::vector<uint8_t> SerializeMessage(
@@ -94,6 +98,7 @@ class Serializer {
bool enabled_machine_id_ = false;
std::string machine_id_;
SNTDecisionCache *decision_cache_;
};
} // namespace santa::santad::logs::endpoint_security::serializers

View File

@@ -24,7 +24,7 @@ namespace es = santa::santad::event_providers::endpoint_security;
namespace santa::santad::logs::endpoint_security::serializers {
Serializer::Serializer() {
Serializer::Serializer(SNTDecisionCache *decision_cache) : decision_cache_(decision_cache) {
if ([[SNTConfigurator configurator] enableMachineIDDecoration]) {
enabled_machine_id_ = true;
machine_id_ = [[[SNTConfigurator configurator] machineID] UTF8String] ?: "";
@@ -46,15 +46,18 @@ std::vector<uint8_t> Serializer::SerializeMessageTemplate(const es::EnrichedExch
return SerializeMessage(msg);
}
std::vector<uint8_t> Serializer::SerializeMessageTemplate(const es::EnrichedExec &msg) {
SNTCachedDecision *cd;
const es_message_t &es_msg = msg.es_msg();
if (es_msg.action_type == ES_ACTION_TYPE_NOTIFY &&
es_msg.action.notify.result.auth == ES_AUTH_RESULT_ALLOW) {
// For allowed execs, cached decision timestamps must be updated
[[SNTDecisionCache sharedCache]
cd = [decision_cache_
resetTimestampForCachedDecision:msg.es_msg().event.exec.target->executable->stat];
} else {
cd = [decision_cache_ cachedDecisionForFile:msg.es_msg().event.exec.target->executable->stat];
}
return SerializeMessage(msg);
return SerializeMessage(msg, cd);
}
std::vector<uint8_t> Serializer::SerializeMessageTemplate(const es::EnrichedExit &msg) {
return SerializeMessage(msg);

View File

@@ -14,6 +14,10 @@
#include "Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h"
#include "Source/common/SantaCache.h"
#import "Source/common/SantaVnode.h"
#include "Source/common/SantaVnodeHash.h"
// These functions are exported by the Security framework, but are not included in headers
extern "C" Boolean SecTranslocateIsTranslocatedURL(CFURLRef path, bool *isTranslocated,
CFErrorRef *__nullable error);
@@ -32,10 +36,17 @@ static inline void SetThreadIDs(uid_t uid, gid_t gid) {
}
NSString *OriginalPathForTranslocation(const es_process_t *es_proc) {
// Cache vnodes that have been determined to not be translocated
static SantaCache<SantaVnode, bool> isNotTranslocatedCache(1024);
if (!es_proc) {
return nil;
}
if (isNotTranslocatedCache.get(SantaVnode::VnodeForFile(es_proc->executable))) {
return nil;
}
// Note: Benchmarks showed better performance using `URLWithString` with a `file://` prefix
// compared to using `fileURLWithPath`.
CFURLRef cfExecURL = (__bridge CFURLRef)
@@ -58,6 +69,8 @@ NSString *OriginalPathForTranslocation(const es_process_t *es_proc) {
if (dropPrivs) {
SetThreadIDs(KAUTH_UID_NONE, KAUTH_GID_NONE);
}
} else {
isNotTranslocatedCache.set(SantaVnode::VnodeForFile(es_proc->executable), true);
}
return [origURL path];

View File

@@ -42,17 +42,18 @@ class File : public Writer, public std::enable_shared_from_this<File> {
~File();
void Write(std::vector<uint8_t> &&bytes) override;
void Flush() override;
friend class santa::santad::logs::endpoint_security::writers::FilePeer;
private:
void OpenFileHandle();
void OpenFileHandleLocked();
void WatchLogFile();
void FlushBuffer();
void FlushLocked();
bool ShouldFlush();
void EnsureCapacity(size_t additional_bytes);
void CopyData(const std::vector<uint8_t> &bytes);
void EnsureCapacityLocked(size_t additional_bytes);
void CopyDataLocked(const std::vector<uint8_t> &bytes);
std::vector<uint8_t> buffer_;
size_t batch_size_bytes_;

View File

@@ -39,7 +39,7 @@ std::shared_ptr<File> File::Create(NSString *path, uint64_t flush_timeout_ms,
if (!shared_writer) {
return;
}
shared_writer->FlushBuffer();
shared_writer->FlushLocked();
});
dispatch_resume(ret_writer->timer_source_);
@@ -55,7 +55,7 @@ File::File(NSString *path, size_t batch_size_bytes, size_t max_expected_write_si
timer_source_(timer_source),
watch_source_(nullptr) {
path_ = path;
OpenFileHandle();
OpenFileHandleLocked();
}
void File::WatchLogFile() {
@@ -69,7 +69,7 @@ void File::WatchLogFile() {
auto shared_this = shared_from_this();
dispatch_source_set_event_handler(watch_source_, ^{
[shared_this->file_handle_ closeFile];
shared_this->OpenFileHandle();
shared_this->OpenFileHandleLocked();
shared_this->WatchLogFile();
});
@@ -83,7 +83,7 @@ File::~File() {
}
// IMPORTANT: Not thread safe.
void File::OpenFileHandle() {
void File::OpenFileHandleLocked() {
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:path_]) {
[fm createFileAtPath:path_ contents:nil attributes:nil];
@@ -101,10 +101,10 @@ void File::Write(std::vector<uint8_t> &&bytes) {
dispatch_async(q_, ^{
std::vector<uint8_t> moved_bytes = std::move(temp_bytes);
shared_this->CopyData(moved_bytes);
shared_this->CopyDataLocked(moved_bytes);
if (shared_this->ShouldFlush()) {
shared_this->FlushBuffer();
shared_this->FlushLocked();
}
});
}
@@ -113,22 +113,28 @@ bool File::ShouldFlush() {
return buffer_offset_ >= batch_size_bytes_;
}
void File::Flush() {
dispatch_sync(q_, ^{
FlushLocked();
});
}
// IMPORTANT: Not thread safe.
void File::EnsureCapacity(size_t additional_bytes) {
void File::EnsureCapacityLocked(size_t additional_bytes) {
if ((buffer_offset_ + additional_bytes) > buffer_.capacity()) {
buffer_.resize(buffer_.capacity() * 2);
}
}
// IMPORTANT: Not thread safe.
void File::CopyData(const std::vector<uint8_t> &bytes) {
EnsureCapacity(bytes.size());
void File::CopyDataLocked(const std::vector<uint8_t> &bytes) {
EnsureCapacityLocked(bytes.size());
std::copy(bytes.begin(), bytes.end(), buffer_.begin() + buffer_offset_);
buffer_offset_ += bytes.size();
}
// IMPORTANT: Not thread safe.
void File::FlushBuffer() {
void File::FlushLocked() {
if (likely(buffer_offset_ > 0)) {
write(file_handle_.fileDescriptor, buffer_.data(), buffer_offset_);

View File

@@ -29,15 +29,34 @@ class FilePeer : public File {
// Make constructors visible
using File::File;
using File::CopyData;
using File::EnsureCapacity;
using File::CopyDataLocked;
using File::EnsureCapacityLocked;
using File::ShouldFlush;
using File::WatchLogFile;
NSFileHandle *FileHandle() { return file_handle_; }
// Member accesses wrapped in a dispatch_sync to satisfy tsan.
NSFileHandle *FileHandle() {
__block NSFileHandle *h;
dispatch_sync(q_, ^{
h = file_handle_;
});
return h;
}
size_t InternalBufferSize() { return buffer_offset_; }
size_t InternalBufferCapacity() { return buffer_.capacity(); }
size_t InternalBufferSize() {
__block size_t s = 0;
dispatch_sync(q_, ^{
s = buffer_offset_;
});
return s;
}
size_t InternalBufferCapacity() {
__block size_t s = 0;
dispatch_sync(q_, ^{
s = buffer_.capacity();
});
return s;
}
};
} // namespace santa::santad::logs::endpoint_security::writers
@@ -192,7 +211,7 @@ bool WaitForBufferSize(std::shared_ptr<FilePeer> file, size_t expectedSize) {
XCTAssertEqual(file->InternalBufferSize(), 0);
XCTAssertEqual(file->InternalBufferCapacity(), initialCapacity);
file->EnsureCapacity(batchSize);
file->EnsureCapacityLocked(batchSize);
// No data was written, so size is still 0
XCTAssertEqual(file->InternalBufferSize(), 0);
@@ -201,7 +220,7 @@ bool WaitForBufferSize(std::shared_ptr<FilePeer> file, size_t expectedSize) {
// the initial amount
XCTAssertEqual(file->InternalBufferCapacity(), initialCapacity);
file->EnsureCapacity(initialCapacity + 100);
file->EnsureCapacityLocked(initialCapacity + 100);
// No data was written, so size is still 0
XCTAssertEqual(file->InternalBufferSize(), 0);
@@ -225,15 +244,15 @@ bool WaitForBufferSize(std::shared_ptr<FilePeer> file, size_t expectedSize) {
XCTAssertEqual(file->InternalBufferSize(), 0);
XCTAssertEqual(file->InternalBufferCapacity(), initialCapacity);
file->CopyData(bytes);
file->CopyDataLocked(bytes);
// After a copy, buffer size should match copied data size
XCTAssertEqual(file->InternalBufferSize(), bytes.size());
// Do a couple more copies that should require the buffer to grow and then
// confirm the size/capacity still matches expectations
file->CopyData(bytes);
file->CopyData(bytes);
file->CopyDataLocked(bytes);
file->CopyDataLocked(bytes);
XCTAssertEqual(file->InternalBufferSize(), bytes.size() * 3);
XCTAssertEqual(file->InternalBufferCapacity(), initialCapacity * 2);
}
@@ -249,7 +268,7 @@ bool WaitForBufferSize(std::shared_ptr<FilePeer> file, size_t expectedSize) {
XCTAssertFalse(file->ShouldFlush());
// Copy some data into the buffer
file->CopyData(bytes);
file->CopyDataLocked(bytes);
// Buffer size should be updated
XCTAssertEqual(file->InternalBufferSize(), bytes.size());
@@ -258,8 +277,8 @@ bool WaitForBufferSize(std::shared_ptr<FilePeer> file, size_t expectedSize) {
XCTAssertFalse(file->ShouldFlush());
// Exceed the batch size
file->CopyData(bytes);
file->CopyData(bytes);
file->CopyDataLocked(bytes);
file->CopyDataLocked(bytes);
// Should want to flush now that the batch size is exceeded
XCTAssertTrue(file->ShouldFlush());

View File

@@ -28,6 +28,7 @@ class Null : public Writer {
static std::shared_ptr<Null> Create();
void Write(std::vector<uint8_t>&& bytes) override;
void Flush() override;
};
} // namespace santa::santad::logs::endpoint_security::writers

View File

@@ -24,4 +24,8 @@ void Null::Write(std::vector<uint8_t> &&bytes) {
// Intentionally do nothing
}
void Null::Flush() {
// Intentionally do nothing
}
} // namespace santa::santad::logs::endpoint_security::writers

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