mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5307bd9b7f | ||
|
|
0622e6de71 | ||
|
|
e7c32ae87d | ||
|
|
deaf3a638c | ||
|
|
8a7f1142a8 | ||
|
|
c180205059 | ||
|
|
337df0aa31 | ||
|
|
e2b099aa50 | ||
|
|
fc4e29f34c | ||
|
|
bf3b6bc6e2 | ||
|
|
b810fc81e1 | ||
|
|
3b3aa999c5 | ||
|
|
59428f3be3 | ||
|
|
ae6451a9b2 | ||
|
|
feac080fa7 | ||
|
|
d0f2a0ac4d | ||
|
|
7fc06ea9d8 | ||
|
|
1dfeeac936 | ||
|
|
ac9b5d9399 | ||
|
|
7f3f1c5448 | ||
|
|
46efd6893f | ||
|
|
50232578d6 | ||
|
|
d83be03a20 | ||
|
|
119b29b534 | ||
|
|
be87b3eaf2 | ||
|
|
0fe672817e | ||
|
|
c3b2fbf512 | ||
|
|
2984d98cb9 | ||
|
|
5295faef0e | ||
|
|
0209344f62 | ||
|
|
53ca5eb811 | ||
|
|
33c7aab9f1 | ||
|
|
f6d837ac31 | ||
|
|
5e0a383662 | ||
|
|
8055b451bb | ||
|
|
c5e7736eef | ||
|
|
61558048c0 | ||
|
|
cf0e3fd3db | ||
|
|
15519c6de8 | ||
|
|
a415679980 | ||
|
|
27ae60e265 | ||
|
|
29a50f072c | ||
|
|
a97e82e316 | ||
|
|
532120ac02 | ||
|
|
ec934854fc | ||
|
|
ad0e2abdac | ||
|
|
dc11ea6534 | ||
|
|
3acf3c1d00 | ||
|
|
41bc3d2542 | ||
|
|
45a5d4e800 | ||
|
|
82bd981f31 | ||
|
|
6480d9c99b | ||
|
|
7e963080b3 | ||
|
|
e58cd7d125 | ||
|
|
db597e413b | ||
|
|
78f46896d5 | ||
|
|
cc0742dbfb | ||
|
|
9c2f76af72 | ||
|
|
a3ed5ccb40 | ||
|
|
b4149816c7 | ||
|
|
2313d6338d | ||
|
|
414fbff721 | ||
|
|
5a2e42e9b4 | ||
|
|
f8d1b2e880 | ||
|
|
5f4d2a92fc | ||
|
|
4ccffdca01 | ||
|
|
e60bbe1b55 | ||
|
|
eee2149439 | ||
|
|
dcbbc33e5e | ||
|
|
ebe5166d77 | ||
|
|
6e5a530df5 | ||
|
|
1e88b88ee6 | ||
|
|
2d74f36ddb | ||
|
|
3a3564f36b | ||
|
|
d3c7cbbcc3 | ||
|
|
1ff6967934 | ||
|
|
53877f6114 | ||
|
|
8c50af4041 | ||
|
|
d0d4508f77 | ||
|
|
df3aac5baf | ||
|
|
e289056e5e | ||
|
|
4adad2ecfa | ||
|
|
dc1a3c27c2 | ||
|
|
a2f8030482 | ||
|
|
338a4f738f | ||
|
|
845d72eebd | ||
|
|
ca81270bff | ||
|
|
42cf1b232a | ||
|
|
57285c48dd | ||
|
|
2279cd8662 | ||
|
|
9423beecc8 | ||
|
|
b18d4a0e30 | ||
|
|
290ebed15e | ||
|
|
435868aa7a | ||
|
|
2e3952a31d | ||
|
|
60f53bc20a | ||
|
|
fec3766da4 | ||
|
|
ae63055f34 | ||
|
|
e5a0c3c1c0 | ||
|
|
5680c69164 | ||
|
|
8a978c1e75 | ||
|
|
6aa7c9ba86 | ||
|
|
6adef6a714 | ||
|
|
1d8c105257 | ||
|
|
e2d7cf04fc | ||
|
|
9d448071f7 | ||
|
|
cd6c0e7120 | ||
|
|
ec5e8177fb | ||
|
|
8e10c103cb | ||
|
|
db6c14ea10 | ||
|
|
4a4f1a971c | ||
|
|
c5c82a18ff | ||
|
|
f702c7a281 | ||
|
|
958ef52698 | ||
|
|
068ec885b2 | ||
|
|
e572f047c0 | ||
|
|
b904a329d9 | ||
|
|
d19343bccd | ||
|
|
09cd78d756 | ||
|
|
f169b69944 | ||
|
|
40f9872c54 | ||
|
|
5718f2e582 | ||
|
|
04fd742114 | ||
|
|
194a3a6d4a | ||
|
|
e1dc50fb36 | ||
|
|
9ff2f0d631 | ||
|
|
85058ec290 | ||
|
|
6e90673f71 | ||
|
|
a58cee908f | ||
|
|
80b26955b4 | ||
|
|
6a84023548 | ||
|
|
e70acefb5c | ||
|
|
41c918ee87 | ||
|
|
1adb6d2726 | ||
|
|
8c531a256b | ||
|
|
5829363733 | ||
|
|
379f283c62 | ||
|
|
2082345c02 | ||
|
|
dd8f81a60e | ||
|
|
8ccb0813f1 | ||
|
|
b24e7e42bf | ||
|
|
4821ebebd5 | ||
|
|
efeaa82618 | ||
|
|
3f3de02644 | ||
|
|
f6c9456ea7 | ||
|
|
2aaff051c8 | ||
|
|
2df7e91c87 | ||
|
|
37644acd01 | ||
|
|
899ca89e23 | ||
|
|
e7281f1c55 | ||
|
|
bf0ca24ae7 | ||
|
|
4fe8b7908f | ||
|
|
a8dd332402 | ||
|
|
6631b0a8e3 | ||
|
|
07e09db608 | ||
|
|
d041a48c97 | ||
|
|
1683e09cc8 | ||
|
|
d6c73e0c6c |
@@ -1,5 +1,6 @@
|
||||
# Ignore reason: These crafted binaries are used in tests
|
||||
ignorePaths:
|
||||
- Fuzzing/common/MachOParse_corpus/ret0
|
||||
- Source/common/testdata/bad_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
|
||||
36
.bazelrc
36
.bazelrc
@@ -6,11 +6,37 @@ build --copt=-Wno-error=deprecated-declarations
|
||||
build --per_file_copt=.*\.mm\$@-std=c++17
|
||||
build --cxxopt=-std=c++17
|
||||
|
||||
build:asan --strip=never
|
||||
build:asan --copt="-Wno-macro-redefined"
|
||||
build:asan --copt="-D_FORTIFY_SOURCE=0"
|
||||
build:asan --copt="-O1"
|
||||
build:asan --copt="-fno-omit-frame-pointer"
|
||||
build --copt=-DSANTA_OPEN_SOURCE=1
|
||||
build --cxxopt=-DSANTA_OPEN_SOURCE=1
|
||||
|
||||
# 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: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
|
||||
|
||||
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
@@ -24,28 +24,28 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
os: [macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=adhoc
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=adhoc
|
||||
|
||||
unit_tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
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
|
||||
|
||||
4
.github/workflows/continuous.yml
vendored
4
.github/workflows/continuous.yml
vendored
@@ -1,13 +1,13 @@
|
||||
name: continuous
|
||||
on:
|
||||
schedule:
|
||||
- cron: '* 10 * * *' # Every day at 10:00 UTC
|
||||
- cron: '0 10 * * *' # Every day at 10:00 UTC
|
||||
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
|
||||
|
||||
jobs:
|
||||
preqs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- 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
|
||||
|
||||
41
.github/workflows/e2e.yml
vendored
Normal file
41
.github/workflows/e2e.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: E2E
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
start_vm:
|
||||
runs-on: e2e-host
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Start VM
|
||||
run: python3 Testing/integration/actions/start_vm.py macOS_12.bundle.tar.gz
|
||||
|
||||
integration:
|
||||
runs-on: e2e-vm
|
||||
env:
|
||||
VM_PASSWORD: ${{ secrets.VM_PASSWORD }}
|
||||
steps:
|
||||
- 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
|
||||
run: echo "/opt/homebrew/bin/" >> $GITHUB_PATH
|
||||
- name: Build, install, and start moroz
|
||||
run: |
|
||||
bazel build @com_github_groob_moroz//cmd/moroz:moroz
|
||||
cp bazel-bin/external/com_github_groob_moroz/cmd/moroz/moroz_/moroz /tmp/moroz
|
||||
/tmp/moroz -configs="$GITHUB_WORKSPACE/Testing/integration/configs/moroz_default/global.toml" -use-tls=false &
|
||||
- name: Build, install, and sync santa
|
||||
run: |
|
||||
bazel run :reload --define=SANTA_BUILD_TYPE=adhoc
|
||||
bazel run //Testing/integration:allow_sysex
|
||||
sudo santactl sync --debug
|
||||
- name: Run integration test binaries
|
||||
run: bazel test //Testing/integration:integration_tests
|
||||
- name: Test config changes
|
||||
run: ./Testing/integration/test_config_changes.sh
|
||||
- name: Test sync server changes
|
||||
run: ./Testing/integration/test_sync_changes.sh
|
||||
- name: Poweroff
|
||||
if: ${{ always() }}
|
||||
run: sudo shutdown -h +1
|
||||
35
.github/workflows/fuzz.yml
vendored
Normal file
35
.github/workflows/fuzz.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Fuzzing
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * *' # Every day at 6:00 UTC
|
||||
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
|
||||
|
||||
jobs:
|
||||
start_vm:
|
||||
runs-on: e2e-host
|
||||
steps:
|
||||
- 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@v3
|
||||
- name: Setup libfuzzer
|
||||
run: Fuzzing/install_libclang_fuzzer.sh
|
||||
- name: Fuzz
|
||||
run: |
|
||||
for target in $(bazel query 'kind(fuzzing_launcher, //Fuzzing:all)'); do
|
||||
bazel run --config=fuzz $target -- -- -max_len=32768 -runs=1000000 -timeout=5
|
||||
done
|
||||
- name: Upload crashes
|
||||
uses: actions/upload-artifact@v1
|
||||
if: failure()
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp/fuzzing/artifacts
|
||||
- name: Poweroff VM
|
||||
if: ${{ always() }}
|
||||
run: sudo shutdown -h +1
|
||||
30
.github/workflows/sanitizers.yml
vendored
Normal file
30
.github/workflows/sanitizers.yml
vendored
Normal 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*
|
||||
429
.pylintrc
Normal file
429
.pylintrc
Normal file
@@ -0,0 +1,429 @@
|
||||
# This Pylint rcfile contains a best-effort configuration to uphold the
|
||||
# best-practices and style described in the Google Python style guide:
|
||||
# https://google.github.io/styleguide/pyguide.html
|
||||
#
|
||||
# Its canonical open-source location is:
|
||||
# https://google.github.io/styleguide/pylintrc
|
||||
|
||||
[MASTER]
|
||||
|
||||
# Files or directories to be skipped. They should be base names, not paths.
|
||||
ignore=third_party
|
||||
|
||||
# Files or directories matching the regex patterns are skipped. The regex
|
||||
# matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=no
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=4
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=abstract-method,
|
||||
apply-builtin,
|
||||
arguments-differ,
|
||||
attribute-defined-outside-init,
|
||||
backtick,
|
||||
bad-option-value,
|
||||
basestring-builtin,
|
||||
buffer-builtin,
|
||||
c-extension-no-member,
|
||||
consider-using-enumerate,
|
||||
cmp-builtin,
|
||||
cmp-method,
|
||||
coerce-builtin,
|
||||
coerce-method,
|
||||
delslice-method,
|
||||
div-method,
|
||||
duplicate-code,
|
||||
eq-without-hash,
|
||||
execfile-builtin,
|
||||
file-builtin,
|
||||
filter-builtin-not-iterating,
|
||||
fixme,
|
||||
getslice-method,
|
||||
global-statement,
|
||||
hex-method,
|
||||
idiv-method,
|
||||
implicit-str-concat,
|
||||
import-error,
|
||||
import-self,
|
||||
import-star-module-level,
|
||||
inconsistent-return-statements,
|
||||
input-builtin,
|
||||
intern-builtin,
|
||||
invalid-str-codec,
|
||||
locally-disabled,
|
||||
long-builtin,
|
||||
long-suffix,
|
||||
map-builtin-not-iterating,
|
||||
misplaced-comparison-constant,
|
||||
missing-function-docstring,
|
||||
metaclass-assignment,
|
||||
next-method-called,
|
||||
next-method-defined,
|
||||
no-absolute-import,
|
||||
no-else-break,
|
||||
no-else-continue,
|
||||
no-else-raise,
|
||||
no-else-return,
|
||||
no-init, # added
|
||||
no-member,
|
||||
no-name-in-module,
|
||||
no-self-use,
|
||||
nonzero-method,
|
||||
oct-method,
|
||||
old-division,
|
||||
old-ne-operator,
|
||||
old-octal-literal,
|
||||
old-raise-syntax,
|
||||
parameter-unpacking,
|
||||
print-statement,
|
||||
raising-string,
|
||||
range-builtin-not-iterating,
|
||||
raw_input-builtin,
|
||||
rdiv-method,
|
||||
reduce-builtin,
|
||||
relative-import,
|
||||
reload-builtin,
|
||||
round-builtin,
|
||||
setslice-method,
|
||||
signature-differs,
|
||||
standarderror-builtin,
|
||||
suppressed-message,
|
||||
sys-max-int,
|
||||
too-few-public-methods,
|
||||
too-many-ancestors,
|
||||
too-many-arguments,
|
||||
too-many-boolean-expressions,
|
||||
too-many-branches,
|
||||
too-many-instance-attributes,
|
||||
too-many-locals,
|
||||
too-many-nested-blocks,
|
||||
too-many-public-methods,
|
||||
too-many-return-statements,
|
||||
too-many-statements,
|
||||
trailing-newlines,
|
||||
unichr-builtin,
|
||||
unicode-builtin,
|
||||
unnecessary-pass,
|
||||
unpacking-in-except,
|
||||
useless-else-on-loop,
|
||||
useless-object-inheritance,
|
||||
useless-suppression,
|
||||
using-cmp-argument,
|
||||
wrong-import-order,
|
||||
xrange-builtin,
|
||||
zip-builtin-not-iterating,
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=main,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=10
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
|
||||
# lines made too long by directives to pytype.
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=(?x)(
|
||||
^\s*(\#\ )?<?https?://\S+>?$|
|
||||
^\s*(from\s+\S+\s+)?import\s+.+$)
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=yes
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=99999
|
||||
|
||||
# String used as indentation unit. The internal Google style guide mandates 2
|
||||
# spaces. Google's externaly-published style guide says 4, consistent with
|
||||
# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
|
||||
# projects (like TensorFlow).
|
||||
indent-string=' '
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=TODO
|
||||
|
||||
|
||||
[STRING]
|
||||
|
||||
# This flag controls whether inconsistent-quotes generates a warning when the
|
||||
# character used as a quote delimiter is used inconsistently within a module.
|
||||
check-quote-consistency=yes
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,_cb
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging,absl.logging,tensorflow.io.logging
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,
|
||||
TERMIOS,
|
||||
Bastion,
|
||||
rexec,
|
||||
sets
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant, absl
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls,
|
||||
class_
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=StandardError,
|
||||
Exception,
|
||||
BaseException
|
||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
* @google/macendpoints
|
||||
11
Fuzzing/BUILD
Normal file
11
Fuzzing/BUILD
Normal file
@@ -0,0 +1,11 @@
|
||||
load("fuzzing.bzl", "objc_fuzz_test")
|
||||
|
||||
objc_fuzz_test(
|
||||
name = "MachOParse",
|
||||
srcs = ["common/MachOParse.mm"],
|
||||
corpus = glob(["common/MachOParse_corpus/*"]),
|
||||
linkopts = ["-lsqlite3"],
|
||||
deps = [
|
||||
"//Source/common:SNTFileInfo",
|
||||
],
|
||||
)
|
||||
40
Fuzzing/common/MachOParse.mm
Normal file
40
Fuzzing/common/MachOParse.mm
Normal file
@@ -0,0 +1,40 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <libproc.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
int get_num_fds() {
|
||||
return proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, NULL, 0) / PROC_PIDLISTFD_SIZE;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
static NSString *tmpPath =
|
||||
[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
|
||||
|
||||
int num_fds_pre = get_num_fds();
|
||||
|
||||
@autoreleasepool {
|
||||
NSData *input = [NSData dataWithBytesNoCopy:(void *)data length:size freeWhenDone:false];
|
||||
[input writeToFile:tmpPath atomically:false];
|
||||
|
||||
NSError *error;
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithResolvedPath:tmpPath error:&error];
|
||||
if (!fi || error != nil) {
|
||||
NSLog(@"Error: %@", error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Mach-O Parsing
|
||||
[fi architectures];
|
||||
[fi isMissingPageZero];
|
||||
[fi infoPlist];
|
||||
}
|
||||
|
||||
if (num_fds_pre != get_num_fds()) {
|
||||
abort();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
Fuzzing/common/MachOParse_corpus/ret0
Normal file
BIN
Fuzzing/common/MachOParse_corpus/ret0
Normal file
Binary file not shown.
20
Fuzzing/fuzzing.bzl
Normal file
20
Fuzzing/fuzzing.bzl
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Utilities for fuzzing Santa"""
|
||||
|
||||
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
|
||||
|
||||
def objc_fuzz_test(name, srcs, deps, corpus, linkopts = [], **kwargs):
|
||||
native.objc_library(
|
||||
name = "%s_lib" % name,
|
||||
srcs = srcs,
|
||||
deps = deps,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
cc_fuzz_test(
|
||||
name = name,
|
||||
deps = [
|
||||
"%s_lib" % name,
|
||||
],
|
||||
linkopts = linkopts,
|
||||
corpus = corpus,
|
||||
)
|
||||
14
Fuzzing/install_libclang_fuzzer.sh
Executable file
14
Fuzzing/install_libclang_fuzzer.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Xcode doesn't include the fuzzer runtime, but the one LLVM ships is compatible with Apple clang.
|
||||
set -uexo pipefail
|
||||
|
||||
CLANG_VERSION=$(clang --version | head -n 1 | cut -d' ' -f 4)
|
||||
DST_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/${CLANG_VERSION}/lib/darwin/libclang_rt.fuzzer_osx.a"
|
||||
|
||||
if [ -f ${DST_PATH} ]; then
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
curl -O -L https://github.com/llvm/llvm-project/releases/download/llvmorg-${CLANG_VERSION}/clang+llvm-${CLANG_VERSION}-x86_64-apple-darwin.tar.xz
|
||||
tar xvf clang+llvm-${CLANG_VERSION}-x86_64-apple-darwin.tar.xz clang+llvm-${CLANG_VERSION}-x86_64-apple-darwin/lib/clang/${CLANG_VERSION}/lib/darwin/libclang_rt.fuzzer_osx.a
|
||||
cp clang+llvm-${CLANG_VERSION}-x86_64-apple-darwin/lib/clang/${CLANG_VERSION}/lib/darwin/libclang_rt.fuzzer_osx.a ${DST_PATH}
|
||||
4
Fuzzing/libFuzzer/.gitignore
vendored
4
Fuzzing/libFuzzer/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
bin
|
||||
llvm-*.src
|
||||
llvm-*.src.tar.xz
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LLVM_VERSION='5.0.1'
|
||||
LLVM_COMPILERRT_TARBALL_NAME="llvm-${LLVM_VERSION}.src.tar.xz"
|
||||
LLVM_COMPILERRT_SRC_FOLDER_NAME=`echo "${LLVM_COMPILERRT_TARBALL_NAME}" | cut -d '.' -f 1-4`
|
||||
LLVM_COMPILERRT_TARBALL_URL="http://releases.llvm.org/${LLVM_VERSION}/${LLVM_COMPILERRT_TARBALL_NAME}"
|
||||
|
||||
LIBFUZZER_FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
LOG_FILE=`mktemp`
|
||||
|
||||
main() {
|
||||
echo "libFuzzer build script"
|
||||
|
||||
echo " > Checking dependencies..."
|
||||
checkDependencies || return 1
|
||||
|
||||
echo " > Entering libFuzzer folder..."
|
||||
cd "${LIBFUZZER_FOLDER}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to enter the libFuzzer folder: ${LIBFUZZER_FOLDER}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -f "${LLVM_COMPILERRT_TARBALL_NAME}" ] ; then
|
||||
echo " > Downloading the LLVM tarball..."
|
||||
curl "${LLVM_COMPILERRT_TARBALL_URL}" -o "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to download the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo " > An existing LLVM tarball was found"
|
||||
fi
|
||||
|
||||
if [ -d "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" ] ; then
|
||||
echo " > Deleting existing LLVM folder..."
|
||||
rm -rf "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing source folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " > Extracting the LLVM tarball..."
|
||||
tar xf "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
rm "${LLVM_COMPILERRT_TARBALL_NAME}" "${LLVM_COMPILERRT_SRC_FOLDER_NAME}"
|
||||
dumpLogFile "Failed to extract the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -d "bin" ] ; then
|
||||
echo " > Deleting existing bin folder..."
|
||||
rm -rf "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing bin folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to create the bin folder"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " > Building libFuzzer..."
|
||||
( cd "bin" && "../${LLVM_COMPILERRT_SRC_FOLDER_NAME}/lib/Fuzzer/build.sh" ) > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to build the library"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf "\nFinished building libFuzzer\n"
|
||||
rm "${LOG_FILE}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkDependencies() {
|
||||
executable_list=( "clang++" "curl" "tar" )
|
||||
|
||||
for executable in "${executable_list[@]}" ; do
|
||||
which "${executable}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "The following program was not found: ${executable}"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
dumpLogFile() {
|
||||
if [ $# -eq 1 ] ; then
|
||||
local message="$1"
|
||||
else
|
||||
local message="An error has occurred"
|
||||
fi
|
||||
|
||||
printf "${message}\n"
|
||||
printf "Log file follows\n===\n"
|
||||
cat "${LOG_FILE}"
|
||||
printf "\n===\n"
|
||||
rm "${LOG_FILE}"
|
||||
}
|
||||
|
||||
main $@
|
||||
exit $?
|
||||
@@ -20,6 +20,7 @@
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > 16) {
|
||||
@@ -28,7 +29,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
return 1;
|
||||
}
|
||||
|
||||
santa_vnode_id_t vnodeID = {};
|
||||
SantaVnode vnodeID = {};
|
||||
std::memcpy(&vnodeID, data, size);
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
@@ -41,14 +42,14 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
|
||||
[[daemonConn remoteObjectProxy]
|
||||
checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
withReply:^(SNTAction action) {
|
||||
if (action == SNTActionRespondAllow) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
} else if (action == SNTActionRespondDeny) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
} else if (action == SNTActionUnset) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;
|
||||
;
|
||||
}
|
||||
|
||||
10
LICENSE
10
LICENSE
@@ -200,3 +200,13 @@
|
||||
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.
|
||||
|
||||
------------------
|
||||
|
||||
Files: Testing/integration/VM/*
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
# Santa [](https://github.com/google/santa/actions/workflows/ci.yml)
|
||||
# Santa
|
||||
|
||||
[](https://github.com/google/santa/blob/main/LICENSE)
|
||||
[](https://github.com/google/santa/actions/workflows/ci.yml)
|
||||
[](https://github.com/google/santa/releases/latest)
|
||||
[](https://github.com/google/santa/releases/latest)
|
||||
[](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" />
|
||||
|
||||
14
SECURITY.md
14
SECURITY.md
@@ -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 pool.sks-keyservers.net:
|
||||
To report vulnerabilities to us privately, either:
|
||||
|
||||
`gpg --keyserver pool.sks-keyservers.net --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`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
@@ -16,26 +16,72 @@ proto_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_proto_library(
|
||||
name = "santa_objc_proto",
|
||||
copts = ["-fno-objc-arc"],
|
||||
non_arc_srcs = ["Santa.pbobjc.m"],
|
||||
protos = [":santa_proto"],
|
||||
cc_proto_library(
|
||||
name = "santa_cc_proto",
|
||||
deps = [":santa_proto"],
|
||||
)
|
||||
|
||||
# Note: Simple wrapper for a `cc_proto_library` target which cannot be directly
|
||||
# depended upon by an `objc_library` target.
|
||||
cc_library(
|
||||
name = "santa_cc_proto_library_wrapper",
|
||||
hdrs = ["santa_proto_include_wrapper.h"],
|
||||
deps = [
|
||||
":santa_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SystemResources",
|
||||
srcs = ["SystemResources.mm"],
|
||||
hdrs = ["SystemResources.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
deps = [":BranchPrediction"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SantaCacheTest",
|
||||
srcs = [
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
srcs = ["SantaCacheTest.mm"],
|
||||
deps = [
|
||||
":SantaCache",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "BranchPrediction",
|
||||
hdrs = ["BranchPrediction.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SantaVnode",
|
||||
hdrs = ["SantaVnode.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "Platform",
|
||||
hdrs = ["Platform.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "String",
|
||||
hdrs = ["String.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SantaVnodeHash",
|
||||
srcs = ["SantaVnodeHash.mm"],
|
||||
hdrs = ["SantaVnodeHash.h"],
|
||||
deps = [
|
||||
":SantaCache",
|
||||
":SantaVnode",
|
||||
],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -66,11 +112,11 @@ objc_library(
|
||||
|
||||
objc_library(
|
||||
name = "SNTCachedDecision",
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
srcs = ["SNTCachedDecision.mm"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SantaVnode",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -78,6 +124,10 @@ objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
module_name = "santa_common_SNTDeviceEvent",
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
@@ -85,13 +135,17 @@ objc_library(
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
textual_hdrs = ["SNTCommonEnums.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTConfigurator",
|
||||
srcs = ["SNTConfigurator.m"],
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
module_name = "santa_common_SNTConfigurator",
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
@@ -134,15 +188,6 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTCommon",
|
||||
hdrs = ["SNTCommon.h"],
|
||||
defines = [
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
@@ -150,18 +195,27 @@ objc_library(
|
||||
deps = [":SNTConfigurator"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTPrefixTree",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = ["-std=c++11"],
|
||||
deps = [":SNTLogging"],
|
||||
objc_library(
|
||||
name = "PrefixTree",
|
||||
hdrs = ["PrefixTree.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "Unit",
|
||||
hdrs = ["Unit.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSyncConstants",
|
||||
@@ -193,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(
|
||||
@@ -273,11 +333,11 @@ objc_library(
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
":SantaVnode",
|
||||
"@MOLCertificate",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -299,9 +359,9 @@ santa_unit_test(
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTPrefixTreeTest",
|
||||
srcs = ["SNTPrefixTreeTest.mm"],
|
||||
deps = [":SNTPrefixTree"],
|
||||
name = "PrefixTreeTest",
|
||||
srcs = ["PrefixTreeTest.mm"],
|
||||
deps = [":PrefixTree"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
@@ -323,11 +383,11 @@ santa_unit_test(
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":PrefixTreeTest",
|
||||
":SNTCachedDecisionTest",
|
||||
":SNTFileInfoTest",
|
||||
":SNTKVOManagerTest",
|
||||
":SNTMetricSetTest",
|
||||
":SNTPrefixTreeTest",
|
||||
":SNTRuleTest",
|
||||
":SantaCacheTest",
|
||||
],
|
||||
@@ -343,6 +403,7 @@ objc_library(
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":SystemResources",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
|
||||
22
Source/common/BranchPrediction.h
Normal file
22
Source/common/BranchPrediction.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/// 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__COMMON__BRANCHPREDICTION_H
|
||||
#define SANTA__COMMON__BRANCHPREDICTION_H
|
||||
|
||||
// Helpful macros to use when the the outcome is largely known
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
#endif
|
||||
34
Source/common/Platform.h
Normal file
34
Source/common/Platform.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__COMMON__PLATFORM_H
|
||||
#define SANTA__COMMON__PLATFORM_H
|
||||
|
||||
#include <Availability.h>
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
#define HAVE_MACOS_12 1
|
||||
#else
|
||||
#define HAVE_MACOS_12 0
|
||||
#endif
|
||||
|
||||
#if defined(MAC_OS_VERSION_13_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
|
||||
#define HAVE_MACOS_13 1
|
||||
#else
|
||||
#define HAVE_MACOS_13 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
302
Source/common/PrefixTree.h
Normal file
302
Source/common/PrefixTree.h
Normal file
@@ -0,0 +1,302 @@
|
||||
/// 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__COMMON__PREFIXTREE_H
|
||||
#define SANTA__COMMON__PREFIXTREE_H
|
||||
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
#if SANTA_PREFIX_TREE_DEBUG
|
||||
#define DEBUG_LOG LOGD
|
||||
#else
|
||||
#define DEBUG_LOG(format, ...) // NOP
|
||||
#endif
|
||||
|
||||
namespace santa::common {
|
||||
|
||||
template <typename ValueT>
|
||||
class PrefixTree {
|
||||
private:
|
||||
// Forward declaration
|
||||
enum class NodeType;
|
||||
class TreeNode;
|
||||
|
||||
public:
|
||||
PrefixTree(uint32_t max_depth = PATH_MAX)
|
||||
: root_(new TreeNode()), max_depth_(max_depth), node_count_(0) {}
|
||||
|
||||
~PrefixTree() { PruneLocked(root_); }
|
||||
|
||||
bool InsertPrefix(const char *s, ValueT value) {
|
||||
absl::MutexLock lock(&lock_);
|
||||
return InsertLocked(s, value, NodeType::kPrefix);
|
||||
}
|
||||
|
||||
bool InsertLiteral(const char *s, ValueT value) {
|
||||
absl::MutexLock lock(&lock_);
|
||||
return InsertLocked(s, value, NodeType::kLiteral);
|
||||
}
|
||||
|
||||
bool HasPrefix(const char *input) {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return HasPrefixLocked(input);
|
||||
}
|
||||
|
||||
std::optional<ValueT> LookupLongestMatchingPrefix(const char *input) {
|
||||
if (!input) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return LookupLongestMatchingPrefixLocked(input);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
absl::MutexLock lock(&lock_);
|
||||
PruneLocked(root_);
|
||||
root_ = new TreeNode();
|
||||
node_count_ = 0;
|
||||
}
|
||||
|
||||
#if SANTA_PREFIX_TREE_DEBUG
|
||||
void Print() {
|
||||
char buf[max_depth_ + 1];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
PrintLocked(root_, buf, 0);
|
||||
}
|
||||
|
||||
uint32_t NodeCount() {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return node_count_;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_)
|
||||
bool InsertLocked(const char *input, ValueT value, NodeType node_type) {
|
||||
const char *p = input;
|
||||
TreeNode *node = root_;
|
||||
|
||||
while (*p) {
|
||||
uint8_t cur_byte = (uint8_t)*p;
|
||||
|
||||
TreeNode *child_node = node->children_[cur_byte];
|
||||
if (!child_node) {
|
||||
// Current node doesn't exist...
|
||||
// Create the rest of the nodes in the tree for the given string
|
||||
|
||||
// Keep a pointer to where this new branch starts from. If the
|
||||
// input length exceeds max_depth, the new branch will need to
|
||||
// be pruned.
|
||||
TreeNode *branch_start_node = node;
|
||||
uint8_t branch_start_byte = (uint8_t)*p;
|
||||
|
||||
do {
|
||||
TreeNode *new_node = new TreeNode();
|
||||
node->children_[cur_byte] = new_node;
|
||||
node = new_node;
|
||||
node_count_++;
|
||||
|
||||
// Check current depth...
|
||||
if (p - input >= max_depth_) {
|
||||
// Attempted to add a string that exceeded max depth
|
||||
// Prune tree from start of this new branch
|
||||
PruneLocked(branch_start_node->children_[branch_start_byte]);
|
||||
branch_start_node->children_[branch_start_byte] = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
cur_byte = (uint8_t) * ++p;
|
||||
} while (*p);
|
||||
|
||||
node->node_type_ = node_type;
|
||||
node->value_ = value;
|
||||
|
||||
return true;
|
||||
} else if (*(p + 1) == '\0') {
|
||||
// Current node exists and we're at the end of our input...
|
||||
// Note: The current node's data will be overwritten
|
||||
|
||||
// Only increment node count if the previous node type wasn't already a
|
||||
// prefix or literal type (in which case it was already counted)
|
||||
if (child_node->node_type_ == NodeType::kInner) {
|
||||
node_count_++;
|
||||
}
|
||||
|
||||
child_node->node_type_ = node_type;
|
||||
child_node->value_ = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
node = child_node;
|
||||
p++;
|
||||
}
|
||||
|
||||
// Should only get here when input is an empty string
|
||||
return false;
|
||||
}
|
||||
|
||||
ABSL_SHARED_LOCKS_REQUIRED(lock_)
|
||||
bool HasPrefixLocked(const char *input) {
|
||||
TreeNode *node = root_;
|
||||
const char *p = input;
|
||||
|
||||
while (*p) {
|
||||
node = node->children_[(uint8_t)*p++];
|
||||
|
||||
if (!node) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (node->node_type_ == NodeType::kPrefix ||
|
||||
(*p == '\0' && node->node_type_ == NodeType::kLiteral)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ABSL_SHARED_LOCKS_REQUIRED(lock_)
|
||||
std::optional<ValueT> LookupLongestMatchingPrefixLocked(const char *input) {
|
||||
TreeNode *node = root_;
|
||||
TreeNode *match = nullptr;
|
||||
const char *p = input;
|
||||
|
||||
while (*p) {
|
||||
node = node->children_[(uint8_t)*p++];
|
||||
|
||||
if (!node) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (node->node_type_ == NodeType::kPrefix ||
|
||||
(*p == '\0' && node->node_type_ == NodeType::kLiteral)) {
|
||||
match = node;
|
||||
}
|
||||
}
|
||||
|
||||
return match ? std::make_optional<ValueT>(match->value_) : std::nullopt;
|
||||
}
|
||||
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_)
|
||||
void PruneLocked(TreeNode *target) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For deep trees, a recursive approach will generate too many stack frames.
|
||||
// Since the depth of the tree is configurable, err on the side of caution
|
||||
// and use a "stack" to walk the tree in a non-recursive manner.
|
||||
TreeNode **stack = new TreeNode *[node_count_ + 1];
|
||||
if (!stack) {
|
||||
LOGE(@"Unable to prune tree!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t count = 0;
|
||||
|
||||
// Seed the "stack" with a starting node.
|
||||
stack[count++] = target;
|
||||
|
||||
// Start at the target node and walk the tree to find and delete all the
|
||||
// sub-nodes.
|
||||
while (count) {
|
||||
TreeNode *node = stack[--count];
|
||||
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
if (!node->children_[i]) {
|
||||
continue;
|
||||
}
|
||||
stack[count++] = node->children_[i];
|
||||
}
|
||||
|
||||
delete node;
|
||||
--node_count_;
|
||||
}
|
||||
|
||||
delete[] stack;
|
||||
}
|
||||
|
||||
#if SANTA_PREFIX_TREE_DEBUG
|
||||
ABSL_SHARED_LOCKS_REQUIRED(lock_)
|
||||
void PrintLocked(TreeNode *node, char *buf, uint32_t depth) {
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
TreeNode *cur_node = node->children_[i];
|
||||
if (cur_node) {
|
||||
buf[depth] = i;
|
||||
if (cur_node->node_type_ != NodeType::kInner) {
|
||||
printf("\t%s (type: %s)\n", buf,
|
||||
cur_node->node_type_ == NodeType::kPrefix ? "prefix" : "literal");
|
||||
}
|
||||
PrintLocked(cur_node, buf, depth + 1);
|
||||
buf[depth] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
enum class NodeType {
|
||||
kInner = 0,
|
||||
kPrefix,
|
||||
kLiteral,
|
||||
};
|
||||
|
||||
///
|
||||
/// TreeNode is a wrapper class that represents one byte.
|
||||
/// 1 node can represent a whole ASCII character.
|
||||
/// For example a pointer to the 'A' node will be stored at children[0x41].
|
||||
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
|
||||
///
|
||||
/// The path for "/🤘" would look like this:
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4]
|
||||
/// -> children[0x98]
|
||||
///
|
||||
/// The path for "/dev" is:
|
||||
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
|
||||
///
|
||||
/// Lookups of children are O(1).
|
||||
///
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2
|
||||
/// byte), would drastically decrease the memory footprint but would double
|
||||
/// required dereferences.
|
||||
///
|
||||
/// TODO(bur): Potentially convert this into a full on radix tree.
|
||||
///
|
||||
class TreeNode {
|
||||
public:
|
||||
TreeNode() : children_(), node_type_(NodeType::kInner) {}
|
||||
~TreeNode() = default;
|
||||
TreeNode *children_[256];
|
||||
PrefixTree::NodeType node_type_;
|
||||
ValueT value_;
|
||||
};
|
||||
|
||||
TreeNode *root_;
|
||||
const uint32_t max_depth_;
|
||||
uint32_t node_count_ ABSL_GUARDED_BY(lock_);
|
||||
absl::Mutex lock_;
|
||||
};
|
||||
|
||||
} // namespace santa::common
|
||||
|
||||
#endif
|
||||
224
Source/common/PrefixTreeTest.mm
Normal file
224
Source/common/PrefixTreeTest.mm
Normal file
@@ -0,0 +1,224 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#define SANTA_PREFIX_TREE_DEBUG 1
|
||||
#include "Source/common/PrefixTree.h"
|
||||
|
||||
using santa::common::PrefixTree;
|
||||
|
||||
@interface PrefixTreeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PrefixTreeTest
|
||||
|
||||
- (void)testBasic {
|
||||
PrefixTree<int> tree;
|
||||
|
||||
XCTAssertFalse(tree.HasPrefix("/foo/bar/baz"));
|
||||
XCTAssertFalse(tree.HasPrefix("/foo/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz"));
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 12));
|
||||
XCTAssertTrue(tree.InsertPrefix("/bar", 34));
|
||||
XCTAssertTrue(tree.InsertLiteral("/foo/bar", 56));
|
||||
|
||||
// Re-inserting something that exists is allowed
|
||||
XCTAssertTrue(tree.InsertLiteral("/foo", 78));
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 56));
|
||||
|
||||
XCTAssertTrue(tree.HasPrefix("/foo/bar/baz"));
|
||||
XCTAssertTrue(tree.HasPrefix("/foo/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz"));
|
||||
|
||||
// Empty strings are not supported
|
||||
XCTAssertFalse(tree.InsertLiteral("", 0));
|
||||
XCTAssertFalse(tree.InsertPrefix("", 0));
|
||||
}
|
||||
|
||||
- (void)testHasPrefix {
|
||||
PrefixTree<int> tree;
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 0));
|
||||
XCTAssertTrue(tree.InsertLiteral("/bar", 0));
|
||||
XCTAssertTrue(tree.InsertLiteral("/baz", 0));
|
||||
XCTAssertTrue(tree.InsertLiteral("/qaz", 0));
|
||||
|
||||
// Check that a tree with a matching prefix is successful
|
||||
XCTAssertTrue(tree.HasPrefix("/foo.txt"));
|
||||
|
||||
// This shouldn't succeed because `/bar` `/baz` and `qaz` are literals
|
||||
XCTAssertFalse(tree.HasPrefix("/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/qaz.txt"));
|
||||
|
||||
// Now change `/bar` to a prefix type and retest HasPrefix
|
||||
// `/bar.txt` should now succeed, but `/baz.txt` should still not pass
|
||||
XCTAssertTrue(tree.InsertPrefix("/bar", 0));
|
||||
XCTAssertTrue(tree.HasPrefix("/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/qaz.txt"));
|
||||
|
||||
// Insert a new prefix string to allow `/baz.txt` to have a valid prefix
|
||||
XCTAssertTrue(tree.InsertPrefix("/b", 0));
|
||||
XCTAssertTrue(tree.HasPrefix("/baz.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/qaz.txt"));
|
||||
|
||||
// An exact match on a literal allows HasPrefix to succeed
|
||||
XCTAssertTrue(tree.InsertLiteral("/qaz.txt", 0));
|
||||
XCTAssertTrue(tree.HasPrefix("/qaz.txt"));
|
||||
}
|
||||
|
||||
- (void)testLookupLongestMatchingPrefix {
|
||||
PrefixTree<int> tree;
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 12));
|
||||
XCTAssertTrue(tree.InsertPrefix("/bar", 34));
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo/bar.txt", 56));
|
||||
|
||||
std::optional<int> value;
|
||||
|
||||
// Matching exact prefix
|
||||
value = tree.LookupLongestMatchingPrefix("/foo");
|
||||
XCTAssertEqual(value.value_or(0), 12);
|
||||
|
||||
// Ensure changing node type works as expected
|
||||
// Literals must match exactly.
|
||||
value = tree.LookupLongestMatchingPrefix("/foo/bar.txt.tmp");
|
||||
XCTAssertEqual(value.value_or(0), 56);
|
||||
XCTAssertTrue(tree.InsertLiteral("/foo/bar.txt", 90));
|
||||
value = tree.LookupLongestMatchingPrefix("/foo/bar.txt.tmp");
|
||||
XCTAssertEqual(value.value_or(0), 12);
|
||||
|
||||
// Inserting over an exiting node returns the new value
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 78));
|
||||
value = tree.LookupLongestMatchingPrefix("/foo");
|
||||
XCTAssertEqual(value.value_or(0), 78);
|
||||
|
||||
// No matching prefix
|
||||
value = tree.LookupLongestMatchingPrefix("/asdf");
|
||||
XCTAssertEqual(value.value_or(0), 0);
|
||||
}
|
||||
|
||||
- (void)testNodeCounts {
|
||||
const uint32_t maxDepth = 100;
|
||||
PrefixTree<int> tree(100);
|
||||
|
||||
XCTAssertEqual(tree.NodeCount(), 0);
|
||||
|
||||
// Start with a small string
|
||||
XCTAssertTrue(tree.InsertPrefix("asdf", 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 4);
|
||||
|
||||
// Add a couple more characters to the existing string
|
||||
XCTAssertTrue(tree.InsertPrefix("asdfgh", 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 6);
|
||||
|
||||
// Inserting a string that exceeds max depth doesn't increase node count
|
||||
XCTAssertFalse(tree.InsertPrefix(std::string(maxDepth + 10, 'A').c_str(), 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 6);
|
||||
|
||||
// Add a new string that is a prefix of an existing string
|
||||
// This should increment the count by one since a new terminal node exists
|
||||
XCTAssertTrue(tree.InsertPrefix("as", 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 7);
|
||||
|
||||
// Re-inserting onto an existing node shouldn't modify the count
|
||||
tree.InsertLiteral("as", 0);
|
||||
tree.InsertPrefix("as", 0);
|
||||
XCTAssertEqual(tree.NodeCount(), 7);
|
||||
}
|
||||
|
||||
- (void)testReset {
|
||||
// Ensure resetting a tree removes all content
|
||||
PrefixTree<int> tree;
|
||||
|
||||
tree.Reset();
|
||||
XCTAssertEqual(tree.NodeCount(), 0);
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("asdf", 0));
|
||||
XCTAssertTrue(tree.InsertPrefix("qwerty", 0));
|
||||
|
||||
XCTAssertTrue(tree.HasPrefix("asdf"));
|
||||
XCTAssertTrue(tree.HasPrefix("qwerty"));
|
||||
XCTAssertEqual(tree.NodeCount(), 10);
|
||||
|
||||
tree.Reset();
|
||||
XCTAssertFalse(tree.HasPrefix("asdf"));
|
||||
XCTAssertFalse(tree.HasPrefix("qwerty"));
|
||||
XCTAssertEqual(tree.NodeCount(), 0);
|
||||
}
|
||||
|
||||
- (void)testComplexValues {
|
||||
class Foo {
|
||||
public:
|
||||
Foo(int x) : x_(x) {}
|
||||
int X() { return x_; }
|
||||
|
||||
private:
|
||||
int x_;
|
||||
};
|
||||
|
||||
PrefixTree<std::shared_ptr<Foo>> tree;
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("foo", std::make_shared<Foo>(123)));
|
||||
XCTAssertTrue(tree.InsertPrefix("bar", std::make_shared<Foo>(456)));
|
||||
|
||||
std::optional<std::shared_ptr<Foo>> value;
|
||||
value = tree.LookupLongestMatchingPrefix("foo");
|
||||
XCTAssertTrue(value.has_value() && value->get()->X() == 123);
|
||||
|
||||
value = tree.LookupLongestMatchingPrefix("bar");
|
||||
XCTAssertTrue(value.has_value() && value->get()->X() == 456);
|
||||
|
||||
value = tree.LookupLongestMatchingPrefix("asdf");
|
||||
XCTAssertFalse(value.has_value());
|
||||
}
|
||||
|
||||
- (void)testThreading {
|
||||
uint32_t count = 4096;
|
||||
auto t = new PrefixTree<int>(count * (uint32_t)[NSUUID UUID].UUIDString.length);
|
||||
|
||||
__block NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
[UUIDs addObject:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
__block _Atomic BOOL stop = NO;
|
||||
|
||||
// Create a bunch of background noise.
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
for (uint64_t i = 0; i < UINT64_MAX; ++i) {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
t->HasPrefix([UUIDs[i % count] UTF8String]);
|
||||
});
|
||||
if (stop) return;
|
||||
}
|
||||
});
|
||||
|
||||
// Fill up the tree.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
XCTAssertEqual(t->InsertPrefix([UUIDs[i] UTF8String], 0), true);
|
||||
});
|
||||
|
||||
// Make sure every leaf byte is found.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
XCTAssertTrue(t->HasPrefix([UUIDs[i] UTF8String]));
|
||||
});
|
||||
|
||||
stop = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,8 +15,8 @@
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SantaVnode.h"
|
||||
|
||||
@class MOLCertificate;
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile;
|
||||
|
||||
@property santa_vnode_id_t vnodeId;
|
||||
@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;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/// Copyright 2015-2022 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,8 +20,7 @@
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_vnodeId.fsid = (uint64_t)esFile->stat.st_dev;
|
||||
_vnodeId.fileid = esFile->stat.st_ino;
|
||||
_vnodeId = SantaVnode::VnodeForFile(esFile);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
- (void)testSNTCachedDecisionInit {
|
||||
// Ensure the vnodeId field is properly set from the es_file_t
|
||||
struct stat sb = MakeStat(1234, 5678);
|
||||
struct stat sb = MakeStat();
|
||||
es_file_t file = MakeESFile("foo", sb);
|
||||
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] initWithEndpointSecurityFile:&file];
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/// Copyright 2015-2022 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Common defines between daemon <-> client
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__COMMON_H
|
||||
#define SANTA__COMMON__COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
typedef enum {
|
||||
ACTION_UNSET,
|
||||
|
||||
// REQUESTS
|
||||
// If an operation is awaiting a cache decision from a similar operation
|
||||
// currently being processed, it will poll about every 5 ms for an answer.
|
||||
ACTION_REQUEST_BINARY,
|
||||
|
||||
// RESPONSES
|
||||
ACTION_RESPOND_ALLOW,
|
||||
ACTION_RESPOND_DENY,
|
||||
ACTION_RESPOND_ALLOW_COMPILER,
|
||||
} santa_action_t;
|
||||
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY || \
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER)
|
||||
|
||||
// Struct to manage vnode IDs
|
||||
typedef struct santa_vnode_id_t {
|
||||
uint64_t fsid;
|
||||
uint64_t fileid;
|
||||
|
||||
#ifdef __cplusplus
|
||||
bool operator==(const santa_vnode_id_t &rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
#endif // SANTA__COMMON__COMMON_H
|
||||
@@ -19,12 +19,30 @@
|
||||
/// The integer values are also stored in the database and so shouldn't be changed.
|
||||
///
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTAction) {
|
||||
SNTActionUnset,
|
||||
|
||||
// REQUESTS
|
||||
// If an operation is awaiting a cache decision from a similar operation
|
||||
// currently being processed, it will poll about every 5 ms for an answer.
|
||||
SNTActionRequestBinary,
|
||||
|
||||
// RESPONSES
|
||||
SNTActionRespondAllow,
|
||||
SNTActionRespondDeny,
|
||||
SNTActionRespondAllowCompiler,
|
||||
};
|
||||
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == SNTActionRespondAllow || x == SNTActionRespondDeny || x == SNTActionRespondAllowCompiler)
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
SNTRuleTypeUnknown,
|
||||
|
||||
SNTRuleTypeBinary = 1,
|
||||
SNTRuleTypeCertificate = 2,
|
||||
SNTRuleTypeTeamID = 3,
|
||||
SNTRuleTypeSigningID = 4,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
@@ -46,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) {
|
||||
@@ -112,12 +132,29 @@ typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
|
||||
SNTSyncStatusTypeUnknown,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTSyncContentEncoding) {
|
||||
SNTSyncContentEncodingNone,
|
||||
SNTSyncContentEncodingDeflate,
|
||||
SNTSyncContentEncodingGzip,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
SNTMetricFormatTypeUnknown,
|
||||
SNTMetricFormatTypeRawJSON,
|
||||
SNTMetricFormatTypeMonarchJSON,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
enum class FileAccessPolicyDecision {
|
||||
kNoPolicy,
|
||||
kDenied,
|
||||
kDeniedInvalidSignature,
|
||||
kAllowed,
|
||||
kAllowedReadAccess,
|
||||
kAllowedAuditOnly,
|
||||
};
|
||||
#endif
|
||||
|
||||
static const char *kSantaDPath =
|
||||
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
|
||||
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
|
||||
@@ -183,10 +183,10 @@
|
||||
/// SNTEventLogTypeSyslog "syslog": Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog "file": Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// SNTEventLogTypeNull "null": Logs nothing
|
||||
/// SNTEventLogTypeProtobuf "protobuf": (BETA) Sent to a file on disk, using maildir format. Use
|
||||
/// mailDirectory to specify a path. Use mailDirectoryFileSizeThresholdKB,
|
||||
/// mailDirectorySizeThresholdMB and mailDirectoryEventMaxFlushTimeSec to configure
|
||||
/// additional maildir format settings.
|
||||
/// SNTEventLogTypeProtobuf "protobuf": (BETA) Sent to a file on disk, using a maildir-like
|
||||
/// format. Use spoolDirectory to specify a path. Use spoolDirectoryFileSizeThresholdKB,
|
||||
/// spoolDirectorySizeThresholdMB and spoolDirectoryEventMaxFlushTimeSec to configure
|
||||
/// additional settings.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
@@ -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.
|
||||
@@ -203,40 +210,67 @@
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectory will provide the base path used for
|
||||
/// saving logs using the maildir format.
|
||||
/// Defaults to /var/db/santa/mail.
|
||||
/// If eventLogType is set to protobuf, spoolDirectory will provide the base path used for
|
||||
/// saving logs using a maildir-like format.
|
||||
/// Defaults to /var/db/santa/spool.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *mailDirectory;
|
||||
@property(readonly, nonatomic) NSString *spoolDirectory;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the mailDirectory.
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the spoolDirectory.
|
||||
/// Defaults to 250.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectoryFileSizeThresholdKB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the spoolDirectory.
|
||||
/// Defaults to 100.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectoryFileSizeThresholdKB;
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the mailDirectory.
|
||||
/// Defaults to 500.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// of time an event will be stored in memory before being written to disk.
|
||||
/// Defaults to 5.0.
|
||||
/// Defaults to 15.0.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) float mailDirectoryEventMaxFlushTimeSec;
|
||||
@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 will be ignored if fileAccessPolicy is set.
|
||||
/// @note: This property is KVO compliant.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fileAccessPolicyPlist;
|
||||
|
||||
///
|
||||
/// If fileAccessPolicyPlist is set, fileAccessPolicyUpdateIntervalSec
|
||||
/// sets the number of seconds between times that the configuration file is
|
||||
/// re-read and policies reconstructed.
|
||||
/// Defaults to 600 seconds (10 minutes)
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) uint32_t fileAccessPolicyUpdateIntervalSec;
|
||||
|
||||
///
|
||||
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
|
||||
@@ -257,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.
|
||||
@@ -368,6 +410,8 @@
|
||||
///
|
||||
@property(nonatomic) BOOL syncCleanRequired;
|
||||
|
||||
#pragma mark - USB Settings
|
||||
|
||||
///
|
||||
/// USB Mount Blocking. Defaults to false.
|
||||
///
|
||||
@@ -379,12 +423,6 @@
|
||||
///
|
||||
@property(nonatomic) NSArray<NSString *> *remountUSBMode;
|
||||
|
||||
///
|
||||
/// When `blockUSBMount` is set, this is the message shown to the user when a device is blocked
|
||||
/// If this message is not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *usbBlockMessage;
|
||||
|
||||
///
|
||||
/// If set, this over-rides the default machine ID used for syncing.
|
||||
///
|
||||
@@ -484,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.
|
||||
///
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -45,6 +46,10 @@
|
||||
/// The hard-coded path to the sync state file.
|
||||
NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
|
||||
|
||||
#ifdef DEBUG
|
||||
NSString *const kConfigOverrideFilePath = @"/var/db/santa/config-overrides.plist";
|
||||
#endif
|
||||
|
||||
/// The domain used by mobileconfig.
|
||||
static NSString *const kMobileConfigDomain = @"com.google.santa";
|
||||
|
||||
@@ -68,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";
|
||||
@@ -88,10 +94,14 @@ static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters
|
||||
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
static NSString *const kEventLogPath = @"EventLogPath";
|
||||
static NSString *const kMailDirectory = @"MailDirectory";
|
||||
static NSString *const kMailDirectoryFileSizeThresholdKB = @"MailDirectoryFileSizeThresholdKB";
|
||||
static NSString *const kMailDirectorySizeThresholdMB = @"MailDirectorySizeThresholdMB";
|
||||
static NSString *const kMailDirectoryEventMaxFlushTimeSec = @"MailDirectoryEventMaxFlushTimeSec";
|
||||
static NSString *const kSpoolDirectory = @"SpoolDirectory";
|
||||
static NSString *const kSpoolDirectoryFileSizeThresholdKB = @"SpoolDirectoryFileSizeThresholdKB";
|
||||
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";
|
||||
|
||||
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
|
||||
|
||||
@@ -99,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";
|
||||
@@ -120,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";
|
||||
@@ -172,7 +180,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBModeKey : array,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kEnableSilentModeKey : string,
|
||||
kEnableSilentModeKey : number,
|
||||
kEnableSilentTTYModeKey : number,
|
||||
kAboutTextKey : string,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
@@ -190,6 +199,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kClientContentEncoding : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
@@ -200,15 +210,17 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
kMailDirectory : string,
|
||||
kMailDirectoryFileSizeThresholdKB : number,
|
||||
kMailDirectorySizeThresholdMB : number,
|
||||
kMailDirectoryEventMaxFlushTimeSec : number,
|
||||
kSpoolDirectory : string,
|
||||
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,
|
||||
@@ -233,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, ^{
|
||||
@@ -391,19 +406,31 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectory {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectory {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryFileSizeThresholdKB {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectoryFileSizeThresholdKB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectorySizeThresholdMB {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectorySizeThresholdMB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryEventMaxFlushTimeSec {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectoryEventMaxFlushTimeSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicy {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyPlist {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyUpdateIntervalSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
@@ -435,10 +462,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -468,15 +491,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBMode {
|
||||
return [self configStateSet];
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBBlockMessage {
|
||||
return [self syncAndConfigStateSet];
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUsbBlockMessage {
|
||||
return [self syncAndConfigStateSet];
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
@@ -577,7 +600,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)remountUSBMode {
|
||||
NSArray<NSString *> *args = self.configState[kRemountUSBModeKey];
|
||||
NSArray<NSString *> *args = self.syncState[kRemountUSBModeKey];
|
||||
if (!args) {
|
||||
args = (NSArray<NSString *> *)self.configState[kRemountUSBModeKey];
|
||||
}
|
||||
for (id arg in args) {
|
||||
if (![arg isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
@@ -616,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];
|
||||
}
|
||||
@@ -679,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];
|
||||
}
|
||||
@@ -756,30 +801,53 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)eventLogTypeRaw {
|
||||
return self.configState[kEventLogType] ?: @"file";
|
||||
}
|
||||
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
- (NSString *)mailDirectory {
|
||||
return self.configState[kMailDirectory] ?: @"/var/db/santa/mail";
|
||||
- (NSString *)spoolDirectory {
|
||||
return self.configState[kSpoolDirectory] ?: @"/var/db/santa/spool";
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectoryFileSizeThresholdKB {
|
||||
return self.configState[kMailDirectoryFileSizeThresholdKB]
|
||||
? [self.configState[kMailDirectoryFileSizeThresholdKB] unsignedIntegerValue]
|
||||
- (NSUInteger)spoolDirectoryFileSizeThresholdKB {
|
||||
return self.configState[kSpoolDirectoryFileSizeThresholdKB]
|
||||
? [self.configState[kSpoolDirectoryFileSizeThresholdKB] unsignedIntegerValue]
|
||||
: 250;
|
||||
}
|
||||
|
||||
- (NSUInteger)spoolDirectorySizeThresholdMB {
|
||||
return self.configState[kSpoolDirectorySizeThresholdMB]
|
||||
? [self.configState[kSpoolDirectorySizeThresholdMB] unsignedIntegerValue]
|
||||
: 100;
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectorySizeThresholdMB {
|
||||
return self.configState[kMailDirectorySizeThresholdMB]
|
||||
? [self.configState[kMailDirectorySizeThresholdMB] unsignedIntegerValue]
|
||||
: 500;
|
||||
- (float)spoolDirectoryEventMaxFlushTimeSec {
|
||||
return self.configState[kSpoolDirectoryEventMaxFlushTimeSec]
|
||||
? [self.configState[kSpoolDirectoryEventMaxFlushTimeSec] floatValue]
|
||||
: 15.0;
|
||||
}
|
||||
|
||||
- (float)mailDirMaxFlushTime {
|
||||
return self.configState[kMailDirectoryEventMaxFlushTimeSec]
|
||||
? [self.configState[kMailDirectoryEventMaxFlushTimeSec] floatValue]
|
||||
: 5.0;
|
||||
- (NSDictionary *)fileAccessPolicy {
|
||||
return self.configState[kFileAccessPolicy];
|
||||
}
|
||||
|
||||
- (NSString *)fileAccessPolicyPlist {
|
||||
// This property is ignored when kFileAccessPolicy is set
|
||||
if (self.configState[kFileAccessPolicy]) {
|
||||
return nil;
|
||||
} else {
|
||||
return self.configState[kFileAccessPolicyPlist];
|
||||
}
|
||||
}
|
||||
|
||||
- (uint32_t)fileAccessPolicyUpdateIntervalSec {
|
||||
return self.configState[kFileAccessPolicyUpdateIntervalSec]
|
||||
? [self.configState[kFileAccessPolicyUpdateIntervalSec] unsignedIntValue]
|
||||
: 60 * 10;
|
||||
}
|
||||
|
||||
- (BOOL)enableMachineIDDecoration {
|
||||
@@ -829,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];
|
||||
}
|
||||
@@ -855,8 +918,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (BOOL)blockUSBMount {
|
||||
NSNumber *number = self.configState[kBlockUSBMountKey];
|
||||
return number ? [number boolValue] : NO;
|
||||
NSNumber *n = self.syncState[kBlockUSBMountKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kBlockUSBMountKey] boolValue];
|
||||
}
|
||||
|
||||
///
|
||||
@@ -987,6 +1052,18 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
forcedConfig[key] = [self expressionForPattern:pattern];
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
NSDictionary *overrides = [NSDictionary dictionaryWithContentsOfFile:kConfigOverrideFilePath];
|
||||
for (NSString *key in overrides) {
|
||||
id obj = overrides[key];
|
||||
if (![obj isKindOfClass:self.forcedConfigKeyTypes[key]]) continue;
|
||||
forcedConfig[key] = obj;
|
||||
if (self.forcedConfigKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [obj isKindOfClass:[NSString class]] ? obj : nil;
|
||||
forcedConfig[key] = [self expressionForPattern:pattern];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return forcedConfig;
|
||||
}
|
||||
|
||||
@@ -1003,12 +1080,50 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
selector:@selector(defaultsChanged:)
|
||||
name:NSUserDefaultsDidChangeNotification
|
||||
object:nil];
|
||||
#ifdef DEBUG
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
||||
[self watchOverridesFile];
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
- (void)watchOverridesFile {
|
||||
while (![[NSFileManager defaultManager] fileExistsAtPath:kConfigOverrideFilePath]) {
|
||||
[NSThread sleepForTimeInterval:0.2];
|
||||
}
|
||||
[self defaultsChanged:nil];
|
||||
|
||||
int descriptor = open([kConfigOverrideFilePath fileSystemRepresentation], O_EVTONLY);
|
||||
if (descriptor < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_source_t source =
|
||||
dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, descriptor,
|
||||
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_DELETE,
|
||||
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0));
|
||||
dispatch_source_set_event_handler(source, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self defaultsChanged:nil];
|
||||
});
|
||||
unsigned long events = dispatch_source_get_data(source);
|
||||
if ((events & DISPATCH_VNODE_DELETE) || (events & DISPATCH_VNODE_RENAME)) {
|
||||
dispatch_source_cancel(source);
|
||||
}
|
||||
});
|
||||
dispatch_source_set_cancel_handler(source, ^{
|
||||
close(descriptor);
|
||||
[self watchOverridesFile];
|
||||
});
|
||||
dispatch_resume(source);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)defaultsChanged:(void *)v {
|
||||
SEL handleChange = @selector(handleChange);
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:handleChange object:nil];
|
||||
[self performSelector:handleChange withObject:nil afterDelay:5.0f];
|
||||
[self performSelector:handleChange withObject:nil afterDelay:1.0f];
|
||||
}
|
||||
|
||||
///
|
||||
@@ -1031,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];
|
||||
|
||||
@@ -572,6 +572,10 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
|
||||
if (!cmdData) return nil;
|
||||
|
||||
if (((struct load_command *)[cmdData bytes])->cmdsize < sizeof(struct load_command)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (is64) {
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT_64 && memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
@@ -642,7 +646,10 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
///
|
||||
- (NSData *)safeSubdataWithRange:(NSRange)range {
|
||||
@try {
|
||||
if ((range.location + range.length) > self.fileSize) return nil;
|
||||
NSUInteger size;
|
||||
if (__builtin_add_overflow(range.location, range.length, &size) || size > self.fileSize) {
|
||||
return nil;
|
||||
}
|
||||
[self.fileHandle seekToFileOffset:range.location];
|
||||
NSData *d = [self.fileHandle readDataOfLength:range.length];
|
||||
if (d.length != range.length) return nil;
|
||||
|
||||
@@ -34,7 +34,12 @@
|
||||
- (void)testPathStandardizing {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/Applications/Safari.app"];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.path, @"/Applications/Safari.app/Contents/MacOS/Safari");
|
||||
if (@available(macOS 13.0, *)) {
|
||||
XCTAssertEqualObjects(sut.path, @"/System/Volumes/Preboot/Cryptexes/App/System/Applications/"
|
||||
@"Safari.app/Contents/MacOS/Safari");
|
||||
} else {
|
||||
XCTAssertEqualObjects(sut.path, @"/Applications/Safari.app/Contents/MacOS/Safari");
|
||||
}
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
|
||||
XCTAssertEqualObjects(sut.path, @"/bin/ls");
|
||||
@@ -90,6 +95,11 @@
|
||||
}
|
||||
|
||||
- (void)testKext {
|
||||
// Skip this test on macOS 13 as KEXTs have moved into the kernelcache.
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc]
|
||||
initWithPath:@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
|
||||
|
||||
@@ -280,15 +280,12 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
if (_fieldNames.count == 0) {
|
||||
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
|
||||
} else {
|
||||
for (NSString *fieldName in _fieldNames) {
|
||||
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
|
||||
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
|
||||
}
|
||||
|
||||
metricDict[@"fields"][fieldName] = fieldVals;
|
||||
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
|
||||
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
|
||||
}
|
||||
metricDict[@"fields"][[_fieldNames componentsJoinedByString:@","]] = fieldVals;
|
||||
}
|
||||
return metricDict;
|
||||
}
|
||||
|
||||
@@ -672,4 +672,35 @@
|
||||
output);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testEnsureMetricsWithMultipleFieldNamesSerializeOnce {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
|
||||
username:@"testUser"];
|
||||
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"client", @"event_type" ]
|
||||
helpText:@"Count of events on the host for a given ES client"];
|
||||
[c incrementBy:1 forFieldValues:@[ @"device_manager", @"auth_mount" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/santa/events" : @{
|
||||
@"description" : @"Count of events on the host for a given ES client",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"client,event_type" : @[
|
||||
@{
|
||||
@"value" : @"device_manager,auth_mount",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
NSDictionary *got = [metricSet export][@"metrics"];
|
||||
XCTAssertEqualObjects(expected, got, @"metrics do not match expected");
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#define LOGD(format, ...) // NOP
|
||||
#define LOGE(format, ...) // NOP
|
||||
|
||||
#define lck_rw_lock_shared(l) pthread_rwlock_rdlock(&l)
|
||||
#define lck_rw_unlock_shared(l) pthread_rwlock_unlock(&l)
|
||||
#define lck_rw_lock_exclusive(l) pthread_rwlock_wrlock(&l)
|
||||
#define lck_rw_unlock_exclusive(l) pthread_rwlock_unlock(&l)
|
||||
|
||||
#define lck_rw_lock_shared_to_exclusive(l) \
|
||||
({ \
|
||||
pthread_rwlock_unlock(&l); \
|
||||
false; \
|
||||
})
|
||||
#define lck_rw_lock_exclusive_to_shared(l) \
|
||||
({ \
|
||||
pthread_rwlock_unlock(&l); \
|
||||
pthread_rwlock_rdlock(&l); \
|
||||
})
|
||||
|
||||
#define lck_mtx_lock(l) l->lock()
|
||||
#define lck_mtx_unlock(l) l->unlock()
|
||||
|
||||
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
max_nodes_ = max_nodes;
|
||||
|
||||
pthread_rwlock_init(&spt_lock_, nullptr);
|
||||
spt_add_lock_ = new std::mutex;
|
||||
}
|
||||
|
||||
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could
|
||||
// overwrite whole branches of another. HasPrefix is still free to read the
|
||||
// tree, until AddPrefix needs to modify it.
|
||||
lck_mtx_lock(spt_add_lock_);
|
||||
|
||||
// Don't allow an empty prefix.
|
||||
if (prefix[0] == '\0') return kIOReturnBadArgument;
|
||||
|
||||
LOGD("Trying to add prefix: %s", prefix);
|
||||
|
||||
// Enforce max tree depth.
|
||||
size_t len = strnlen(prefix, max_nodes_);
|
||||
|
||||
// Grab a shared lock until a new branch is required.
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
// If there is a node in the path that is considered a prefix, stop adding.
|
||||
// For our purposes we only care about the shortest path that matches.
|
||||
if (node->isPrefix) break;
|
||||
|
||||
// Only process a byte at a time.
|
||||
uint8_t value = (uint8_t)prefix[i];
|
||||
|
||||
// Create the child if it does not exist.
|
||||
if (!node->children[value]) {
|
||||
// Upgrade the shared lock.
|
||||
// If the upgrade fails, the shared lock is released.
|
||||
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
|
||||
// Grab a new exclusive lock.
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
// Is there enough room for the rest of the prefix?
|
||||
if ((node_count_ + (len - i)) > max_nodes_) {
|
||||
LOGE("Prefix tree is full, can not add: %s", prefix);
|
||||
|
||||
if (node_count) *node_count = node_count_;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
lck_mtx_unlock(spt_add_lock_);
|
||||
return kIOReturnNoResources;
|
||||
}
|
||||
|
||||
// Create the rest of the prefix.
|
||||
while (i < len) {
|
||||
value = (uint8_t)prefix[i++];
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
node->children[value] = new_node;
|
||||
++node_count_;
|
||||
|
||||
node = new_node;
|
||||
}
|
||||
|
||||
// This is the end, mark the node as a prefix.
|
||||
LOGD("Added prefix: %s", prefix);
|
||||
|
||||
node->isPrefix = true;
|
||||
|
||||
// Downgrade the exclusive lock
|
||||
lck_rw_lock_exclusive_to_shared(spt_lock_);
|
||||
} else if (i + 1 == len) {
|
||||
// If the child does exist and it is the end...
|
||||
// Set the new, higher prefix and prune the now dead nodes.
|
||||
|
||||
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
PruneNode(node->children[value]);
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
new_node->isPrefix = true;
|
||||
|
||||
node->children[value] = new_node;
|
||||
++node_count_;
|
||||
|
||||
LOGD("Added prefix: %s", prefix);
|
||||
|
||||
lck_rw_lock_exclusive_to_shared(spt_lock_);
|
||||
}
|
||||
|
||||
// Get ready for the next iteration.
|
||||
node = node->children[value];
|
||||
}
|
||||
|
||||
if (node_count) *node_count = node_count_;
|
||||
|
||||
lck_rw_unlock_shared(spt_lock_);
|
||||
lck_mtx_unlock(spt_add_lock_);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
bool SNTPrefixTree::HasPrefix(const char *string) {
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
auto found = false;
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
|
||||
// A well formed tree will always break this loop. Even if string doesn't
|
||||
// terminate.
|
||||
const char *p = string;
|
||||
while (*p) {
|
||||
// Only process a byte at a time.
|
||||
node = node->children[(uint8_t)*p++];
|
||||
|
||||
// If it doesn't exist in the tree, no match.
|
||||
if (!node) break;
|
||||
|
||||
// If it does exist, is it a prefix?
|
||||
if (node->isPrefix) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lck_rw_unlock_shared(spt_lock_);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void SNTPrefixTree::Reset() {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
|
||||
PruneNode(root_);
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
|
||||
if (!target) return;
|
||||
|
||||
// For deep trees, a recursive approach will generate too many stack frames.
|
||||
// Make a "stack" and walk the tree.
|
||||
auto stack = new SantaPrefixNode *[node_count_ + 1];
|
||||
if (!stack) {
|
||||
LOGE("Unable to prune tree!");
|
||||
|
||||
return;
|
||||
}
|
||||
auto count = 0;
|
||||
|
||||
// Seed the "stack" with a starting node.
|
||||
stack[count++] = target;
|
||||
|
||||
// Start at the target node and walk the tree to find and delete all the
|
||||
// sub-nodes.
|
||||
while (count) {
|
||||
auto node = stack[--count];
|
||||
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
if (!node->children[i]) continue;
|
||||
stack[count++] = node->children[i];
|
||||
}
|
||||
|
||||
delete node;
|
||||
--node_count_;
|
||||
}
|
||||
|
||||
delete[] stack;
|
||||
}
|
||||
|
||||
SNTPrefixTree::~SNTPrefixTree() {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
PruneNode(root_);
|
||||
root_ = nullptr;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
|
||||
pthread_rwlock_destroy(&spt_lock_);
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H
|
||||
#define SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H
|
||||
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
// Support for unit testing.
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
///
|
||||
/// SantaPrefixTree is a simple prefix tree implementation.
|
||||
/// Operations are thread safe.
|
||||
///
|
||||
class SNTPrefixTree {
|
||||
public:
|
||||
// Add a prefix to the tree.
|
||||
// Optionally pass node_count to get the number of nodes after the add.
|
||||
IOReturn AddPrefix(const char *, uint64_t *node_count = nullptr);
|
||||
|
||||
// Check if the tree has a prefix for string.
|
||||
bool HasPrefix(const char *string);
|
||||
|
||||
// Reset the tree.
|
||||
void Reset();
|
||||
|
||||
SNTPrefixTree(uint32_t max_nodes = kDefaultMaxNodes);
|
||||
~SNTPrefixTree();
|
||||
|
||||
private:
|
||||
///
|
||||
/// SantaPrefixNode is a wrapper class that represents one byte.
|
||||
/// 1 node can represent a whole ASCII character.
|
||||
/// For example a pointer to the 'A' node will be stored at children[0x41].
|
||||
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
|
||||
///
|
||||
/// The path for "/🤘" would look like this:
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4]
|
||||
/// -> children[0x98]
|
||||
///
|
||||
/// The path for "/dev" is:
|
||||
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
|
||||
///
|
||||
/// Lookups of children are O(1).
|
||||
///
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2
|
||||
/// byte), would drastically decrease the memory footprint but would double
|
||||
/// required dereferences.
|
||||
///
|
||||
/// TODO(bur): Potentially convert this into a full on radix tree.
|
||||
///
|
||||
class SantaPrefixNode {
|
||||
public:
|
||||
bool isPrefix;
|
||||
SantaPrefixNode *children[256];
|
||||
};
|
||||
|
||||
// PruneNode will remove the passed in node from the tree.
|
||||
// The passed in node and all subnodes will be deleted.
|
||||
// It is the caller's responsibility to reset the pointer to this node (held
|
||||
// by the parent). If the tree is in use grab the exclusive lock.
|
||||
void PruneNode(SantaPrefixNode *);
|
||||
|
||||
SantaPrefixNode *root_;
|
||||
|
||||
// Each node takes up ~2k, assuming MAXPATHLEN is 1024 max out at ~2MB.
|
||||
static const uint32_t kDefaultMaxNodes = MAXPATHLEN;
|
||||
uint32_t max_nodes_;
|
||||
uint32_t node_count_;
|
||||
|
||||
pthread_rwlock_t spt_lock_;
|
||||
std::mutex *spt_add_lock_;
|
||||
};
|
||||
|
||||
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */
|
||||
@@ -1,73 +0,0 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
@interface SNTPrefixTreeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTPrefixTreeTest
|
||||
|
||||
- (void)testAddAndHas {
|
||||
auto t = SNTPrefixTree();
|
||||
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
|
||||
t.AddPrefix("/private/var/tmp/");
|
||||
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
|
||||
}
|
||||
|
||||
- (void)testReset {
|
||||
auto t = SNTPrefixTree();
|
||||
t.AddPrefix("/private/var/tmp/");
|
||||
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
|
||||
t.Reset();
|
||||
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
|
||||
}
|
||||
|
||||
- (void)testThreading {
|
||||
uint32_t count = 4096;
|
||||
auto t = new SNTPrefixTree(count * (uint32_t)[NSUUID UUID].UUIDString.length);
|
||||
|
||||
NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
[UUIDs addObject:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
__block BOOL stop = NO;
|
||||
|
||||
// Create a bunch of background noise.
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
for (uint64_t i = 0; i < UINT64_MAX; ++i) {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
t->HasPrefix([UUIDs[i % count] UTF8String]);
|
||||
});
|
||||
if (stop) return;
|
||||
}
|
||||
});
|
||||
|
||||
// Fill up the tree.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
XCTAssertEqual(t->AddPrefix([UUIDs[i] UTF8String]), kIOReturnSuccess);
|
||||
});
|
||||
|
||||
// Make sure every leaf byte is found.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
XCTAssertTrue(t->HasPrefix([UUIDs[i] UTF8String]));
|
||||
});
|
||||
|
||||
stop = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
}];
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID
|
||||
signingID:(NSString *)signingID
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
|
||||
///
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SantaVnode.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@@ -31,13 +31,13 @@
|
||||
/// Cache Ops
|
||||
///
|
||||
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
|
||||
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
|
||||
- (void)checkCacheForVnodeID:(SantaVnode)vnodeID withReply:(void (^)(SNTAction))reply;
|
||||
|
||||
///
|
||||
/// 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,12 +57,14 @@
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID
|
||||
signingID:(NSString *)signingID
|
||||
reply:(void (^)(SNTEventState))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
///
|
||||
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
|
||||
- (void)watchItemsState:(void (^)(BOOL, uint64_t, NSString *, NSString *, NSTimeInterval))reply;
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply;
|
||||
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "Source/common/SNTCommon.h"
|
||||
#include "Source/common/BranchPrediction.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
44
Source/common/SantaVnode.h
Normal file
44
Source/common/SantaVnode.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/// 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__COMMON__SANTAVNODE_H
|
||||
#define SANTA__COMMON__SANTAVNODE_H
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// Struct to manage vnode IDs
|
||||
typedef struct SantaVnode {
|
||||
dev_t fsid;
|
||||
ino_t fileid;
|
||||
|
||||
#ifdef __cplusplus
|
||||
bool operator==(const SantaVnode &rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
|
||||
static inline SantaVnode VnodeForFile(const struct stat &sb) {
|
||||
return SantaVnode{
|
||||
.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;
|
||||
|
||||
#endif
|
||||
24
Source/common/SantaVnodeHash.h
Normal file
24
Source/common/SantaVnodeHash.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/// 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__COMMON__SANTAVNODEHASH_H
|
||||
#define SANTA__COMMON__SANTAVNODEHASH_H
|
||||
|
||||
#include "Source/common/SantaCache.h"
|
||||
#include "Source/common/SantaVnode.h"
|
||||
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<SantaVnode>(SantaVnode const &t);
|
||||
|
||||
#endif
|
||||
20
Source/common/SantaVnodeHash.mm
Normal file
20
Source/common/SantaVnodeHash.mm
Normal file
@@ -0,0 +1,20 @@
|
||||
/// 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/common/SantaVnodeHash.h"
|
||||
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<SantaVnode>(SantaVnode const &t) {
|
||||
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
|
||||
}
|
||||
35
Source/common/String.h
Normal file
35
Source/common/String.h
Normal 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
|
||||
44
Source/common/SystemResources.h
Normal file
44
Source/common/SystemResources.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/// 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__COMMON__SYSTEMRESOURCES_H
|
||||
#define SANTA__COMMON__SYSTEMRESOURCES_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/proc_info.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
struct SantaTaskInfo {
|
||||
uint64_t virtual_size;
|
||||
uint64_t resident_size;
|
||||
uint64_t total_user_nanos;
|
||||
uint64_t total_system_nanos;
|
||||
};
|
||||
|
||||
// Convert mach absolute time to nanoseconds
|
||||
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();
|
||||
|
||||
#endif
|
||||
79
Source/common/SystemResources.mm
Normal file
79
Source/common/SystemResources.mm
Normal file
@@ -0,0 +1,79 @@
|
||||
/// 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/common/SystemResources.h"
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <libproc.h>
|
||||
#include <mach/kern_return.h>
|
||||
#include <unistd.h>
|
||||
#include <optional>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
|
||||
static mach_timebase_info_data_t GetTimebase() {
|
||||
static dispatch_once_t once_token;
|
||||
static mach_timebase_info_data_t timebase;
|
||||
|
||||
dispatch_once(&once_token, ^{
|
||||
if (mach_timebase_info(&timebase) != KERN_SUCCESS) {
|
||||
// This shouldn't fail. Assume transitory and exit the program.
|
||||
// Hopefully fixes itself on restart...
|
||||
LOGE(@"Failed to get timebase info. Exiting.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
});
|
||||
|
||||
return timebase;
|
||||
}
|
||||
|
||||
uint64_t MachTimeToNanos(uint64_t mach_time) {
|
||||
static mach_timebase_info_data_t timebase = GetTimebase();
|
||||
|
||||
return mach_time * timebase.numer / timebase.denom;
|
||||
}
|
||||
|
||||
uint64_t NanosToMachTime(uint64_t nanos) {
|
||||
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;
|
||||
|
||||
if (proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &pti, PROC_PIDTASKINFO_SIZE) <
|
||||
PROC_PIDTASKINFO_SIZE) {
|
||||
LOGW(@"Unable to get system resource information");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return SantaTaskInfo{
|
||||
.virtual_size = pti.pti_virtual_size,
|
||||
.resident_size = pti.pti_resident_size,
|
||||
.total_user_nanos = MachTimeToNanos(pti.pti_total_user),
|
||||
.total_system_nanos = MachTimeToNanos(pti.pti_total_system),
|
||||
};
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define NOBODY_UID ((unsigned int)-2)
|
||||
#define NOBODY_GID ((unsigned int)-2)
|
||||
#define NOGROUP_GID ((unsigned int)-1)
|
||||
|
||||
// Bubble up googletest expectation failures to XCTest failures
|
||||
#define XCTBubbleMockVerifyAndClearExpectations(mock) \
|
||||
@@ -38,6 +38,10 @@
|
||||
// Pretty print C++ string match errors
|
||||
#define XCTAssertCppStringEqual(got, want) XCTAssertCStringEqual((got).c_str(), (want).c_str())
|
||||
|
||||
#define XCTAssertSemaTrue(s, sec, m) \
|
||||
XCTAssertEqual( \
|
||||
0, dispatch_semaphore_wait((s), dispatch_time(DISPATCH_TIME_NOW, (sec)*NSEC_PER_SEC)), m)
|
||||
|
||||
// Helper to ensure at least `ms` milliseconds are slept, even if the sleep
|
||||
// function returns early due to interrupts.
|
||||
void SleepMS(long ms);
|
||||
@@ -47,9 +51,18 @@ enum class ActionType {
|
||||
Notify,
|
||||
};
|
||||
|
||||
//
|
||||
// Helpers to construct various ES structs
|
||||
//
|
||||
|
||||
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver);
|
||||
struct stat MakeStat(ino_t ino, dev_t devno = 0);
|
||||
|
||||
/// Construct a `struct stat` buffer with each member having a unique value.
|
||||
/// @param offset An optional offset to be added to each member. useful when
|
||||
/// a test has multiple stats and you'd like for them each to have different
|
||||
/// values across the members.
|
||||
struct stat MakeStat(int offset = 0);
|
||||
|
||||
es_string_token_t MakeESStringToken(const char *s);
|
||||
es_file_t MakeESFile(const char *path, struct stat sb = {});
|
||||
es_process_t MakeESProcess(es_file_t *file, audit_token_t tok = {}, audit_token_t parent_tok = {});
|
||||
@@ -57,4 +70,6 @@ es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc,
|
||||
ActionType action_type = ActionType::Notify,
|
||||
uint64_t future_deadline_ms = 100000);
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS();
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <time.h>
|
||||
#include <uuid/uuid.h>
|
||||
#include "Source/common/SystemResources.h"
|
||||
|
||||
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver) {
|
||||
return audit_token_t{
|
||||
@@ -25,9 +27,9 @@ audit_token_t MakeAuditToken(pid_t pid, pid_t pidver) {
|
||||
{
|
||||
0,
|
||||
NOBODY_UID,
|
||||
NOBODY_GID,
|
||||
NOGROUP_GID,
|
||||
NOBODY_UID,
|
||||
NOBODY_GID,
|
||||
NOGROUP_GID,
|
||||
(unsigned int)pid,
|
||||
0,
|
||||
(unsigned int)pidver,
|
||||
@@ -35,10 +37,24 @@ audit_token_t MakeAuditToken(pid_t pid, pid_t pidver) {
|
||||
};
|
||||
}
|
||||
|
||||
struct stat MakeStat(ino_t ino, dev_t devno) {
|
||||
struct stat MakeStat(int offset) {
|
||||
return (struct stat){
|
||||
.st_dev = devno,
|
||||
.st_ino = ino,
|
||||
.st_dev = 1 + offset,
|
||||
.st_mode = (mode_t)(2 + offset),
|
||||
.st_nlink = (nlink_t)(3 + offset),
|
||||
.st_ino = (uint64_t)(4 + offset),
|
||||
.st_uid = NOBODY_UID,
|
||||
.st_gid = NOGROUP_GID,
|
||||
.st_rdev = 5 + offset,
|
||||
.st_atimespec = {.tv_sec = 100 + offset, .tv_nsec = 200 + offset},
|
||||
.st_mtimespec = {.tv_sec = 101 + offset, .tv_nsec = 21 + offset},
|
||||
.st_ctimespec = {.tv_sec = 102 + offset, .tv_nsec = 202 + offset},
|
||||
.st_birthtimespec = {.tv_sec = 103 + offset, .tv_nsec = 203 + offset},
|
||||
.st_size = 6 + offset,
|
||||
.st_blocks = 7 + offset,
|
||||
.st_blksize = 8 + offset,
|
||||
.st_flags = (uint32_t)(9 + offset),
|
||||
.st_gen = (uint32_t)(10 + offset),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,38 +78,43 @@ es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t par
|
||||
.audit_token = tok,
|
||||
.ppid = audit_token_to_pid(parent_tok),
|
||||
.original_ppid = audit_token_to_pid(parent_tok),
|
||||
.group_id = 111,
|
||||
.session_id = 222,
|
||||
.is_platform_binary = true,
|
||||
.is_es_client = true,
|
||||
.executable = file,
|
||||
.parent_audit_token = parent_tok,
|
||||
};
|
||||
}
|
||||
|
||||
static uint64_t AddMillisToMachTime(uint64_t ms, uint64_t machTime) {
|
||||
static dispatch_once_t onceToken;
|
||||
static mach_timebase_info_data_t timebase;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
mach_timebase_info(&timebase);
|
||||
});
|
||||
|
||||
// Convert given machTime to nanoseconds
|
||||
uint64_t nanoTime = machTime * timebase.numer / timebase.denom;
|
||||
|
||||
// Add the ms offset
|
||||
nanoTime += (ms * NSEC_PER_MSEC);
|
||||
|
||||
// Convert back to machTime
|
||||
return nanoTime * timebase.denom / timebase.numer;
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
|
||||
// Note: ES message v3 was only in betas.
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return 6;
|
||||
} else if (@available(macOS 12.3, *)) {
|
||||
return 5;
|
||||
} else if (@available(macOS 11.0, *)) {
|
||||
return 4;
|
||||
} else if (@available(macOS 10.15.4, *)) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType action_type,
|
||||
uint64_t future_deadline_ms) {
|
||||
return es_message_t{
|
||||
.deadline = AddMillisToMachTime(future_deadline_ms, mach_absolute_time()),
|
||||
es_message_t es_msg = {
|
||||
.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,
|
||||
.event_type = et,
|
||||
};
|
||||
|
||||
es_msg.version = MaxSupportedESMessageVersionForCurrentOS();
|
||||
|
||||
return es_msg;
|
||||
}
|
||||
|
||||
void SleepMS(long ms) {
|
||||
|
||||
24
Source/common/Unit.h
Normal file
24
Source/common/Unit.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/// 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__COMMON__UNIT_H
|
||||
#define SANTA__COMMON__UNIT_H
|
||||
|
||||
namespace santa::common {
|
||||
|
||||
struct Unit {};
|
||||
|
||||
} // namespace santa::common
|
||||
|
||||
#endif
|
||||
@@ -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";
|
||||
|
||||
@@ -11,135 +7,530 @@ import "google/protobuf/timestamp.proto";
|
||||
|
||||
option objc_class_prefix = "SNTPB";
|
||||
|
||||
package santa;
|
||||
package santa.pb.v1;
|
||||
|
||||
message ProcessInfo {
|
||||
// User ID and associated username
|
||||
message UserInfo {
|
||||
optional int32 uid = 1;
|
||||
optional string name = 2;
|
||||
}
|
||||
|
||||
// Group ID and associated group name
|
||||
message GroupInfo {
|
||||
optional int32 gid = 1;
|
||||
optional string name = 2;
|
||||
}
|
||||
|
||||
// A process is uniquely identified on macOS by its pid and pidversion
|
||||
message ProcessID {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional int32 ppid = 3;
|
||||
optional int32 uid = 4;
|
||||
optional string user = 5;
|
||||
optional int32 gid = 6;
|
||||
optional string group = 7;
|
||||
}
|
||||
|
||||
message FileModification {
|
||||
enum Action {
|
||||
ACTION_UNKNOWN = 0;
|
||||
ACTION_DELETE = 1;
|
||||
ACTION_EXCHANGE = 2;
|
||||
ACTION_LINK = 3;
|
||||
ACTION_RENAME = 4;
|
||||
ACTION_WRITE = 5;
|
||||
// Code signature information
|
||||
message CodeSignature {
|
||||
// The code directory hash identifies a specific version of a program
|
||||
optional bytes cdhash = 1;
|
||||
|
||||
// The signing id of the code signature
|
||||
optional string signing_id = 2;
|
||||
|
||||
// The team id of the code signature
|
||||
optional string team_id = 3;
|
||||
}
|
||||
|
||||
// Stat information for a file
|
||||
// Mimics data from `stat(2)`
|
||||
message Stat {
|
||||
optional int32 dev = 1;
|
||||
optional uint32 mode = 2;
|
||||
optional uint32 nlink = 3;
|
||||
optional uint64 ino = 4;
|
||||
optional UserInfo user = 5;
|
||||
optional GroupInfo group = 6;
|
||||
optional int32 rdev = 7;
|
||||
optional google.protobuf.Timestamp access_time = 8;
|
||||
optional google.protobuf.Timestamp modification_time = 9;
|
||||
optional google.protobuf.Timestamp change_time = 10;
|
||||
optional google.protobuf.Timestamp birth_time = 11;
|
||||
optional int64 size = 12;
|
||||
optional int64 blocks = 13;
|
||||
optional int32 blksize = 14;
|
||||
optional uint32 flags = 15;
|
||||
optional int32 gen = 16;
|
||||
}
|
||||
|
||||
// Hash value and metadata describing hash algorithm used
|
||||
message Hash {
|
||||
enum HashAlgo {
|
||||
HASH_ALGO_UNKNOWN = 0;
|
||||
HASH_ALGO_SHA256 = 1;
|
||||
}
|
||||
|
||||
optional Action action = 1;
|
||||
optional string path = 2;
|
||||
optional string newpath = 3;
|
||||
optional string process = 4;
|
||||
optional string process_path = 5;
|
||||
optional ProcessInfo process_info = 6;
|
||||
optional string machine_id = 7;
|
||||
optional HashAlgo type = 1;
|
||||
optional string hash = 2;
|
||||
}
|
||||
|
||||
// File information
|
||||
message FileInfo {
|
||||
// File path
|
||||
optional string path = 1;
|
||||
|
||||
// Whether or not the path is truncated
|
||||
optional bool truncated = 2;
|
||||
|
||||
// Stat information
|
||||
optional Stat stat = 3;
|
||||
|
||||
// Hash of file contents
|
||||
optional Hash hash = 4;
|
||||
}
|
||||
|
||||
// Light variant of `FileInfo` message to help minimize on-disk/on-wire sizes
|
||||
message FileInfoLight {
|
||||
// File path
|
||||
optional string path = 1;
|
||||
|
||||
// Whether or not the path is truncated
|
||||
optional bool truncated = 2;
|
||||
}
|
||||
|
||||
// File descriptor information
|
||||
message FileDescriptor {
|
||||
// Enum types gathered from `<sys/proc_info.h>`
|
||||
enum FDType {
|
||||
FD_TYPE_UNKNOWN = 0;
|
||||
FD_TYPE_ATALK = 1;
|
||||
FD_TYPE_VNODE = 2;
|
||||
FD_TYPE_SOCKET = 3;
|
||||
FD_TYPE_PSHM = 4;
|
||||
FD_TYPE_PSEM = 5;
|
||||
FD_TYPE_KQUEUE = 6;
|
||||
FD_TYPE_PIPE = 7;
|
||||
FD_TYPE_FSEVENTS = 8;
|
||||
FD_TYPE_NETPOLICY = 9;
|
||||
FD_TYPE_CHANNEL = 10;
|
||||
FD_TYPE_NEXUS = 11;
|
||||
}
|
||||
|
||||
// File descriptor value
|
||||
optional int32 fd = 1;
|
||||
|
||||
// Type of file object
|
||||
optional FDType fd_type = 2;
|
||||
|
||||
// Unique id of the pipe for correlation with other file descriptors
|
||||
// pointing to the same or other end of the same pipe
|
||||
// Note: Only valid when `fd_type` is `FD_TYPE_PIPE`
|
||||
optional uint64 pipe_id = 3;
|
||||
}
|
||||
|
||||
// Process information
|
||||
message ProcessInfo {
|
||||
// Process ID of the process
|
||||
optional ProcessID id = 1;
|
||||
|
||||
// Process ID of the parent process
|
||||
optional ProcessID parent_id = 2;
|
||||
|
||||
// Process ID of the process responsible for this one
|
||||
optional ProcessID responsible_id = 3;
|
||||
|
||||
// Original parent ID, remains stable in the event a process is reparented
|
||||
optional int32 original_parent_pid = 4;
|
||||
|
||||
// Process group id the process belongs to
|
||||
optional int32 group_id = 5;
|
||||
|
||||
// Session id the process belongs to
|
||||
optional int32 session_id = 6;
|
||||
|
||||
// Effective user/group info
|
||||
optional UserInfo effective_user = 7;
|
||||
optional GroupInfo effective_group = 8;
|
||||
|
||||
// Real user/group info
|
||||
optional UserInfo real_user = 9;
|
||||
optional GroupInfo real_group = 10;
|
||||
|
||||
// Whether or not the process was signed with Apple certificates
|
||||
optional bool is_platform_binary = 11;
|
||||
|
||||
// Whether or not the process is an ES client
|
||||
optional bool is_es_client = 12;
|
||||
|
||||
// Code signature information for the process
|
||||
optional CodeSignature code_signature = 13;
|
||||
|
||||
// Codesigning flags for the process (from `<Kernel/kern/cs_blobs.h>`)
|
||||
optional uint32 cs_flags = 14;
|
||||
|
||||
// File information for the executable backing this process
|
||||
optional FileInfo executable = 15;
|
||||
|
||||
// File information for the associated TTY
|
||||
optional FileInfoLight tty = 16;
|
||||
|
||||
// Time the process was started
|
||||
optional google.protobuf.Timestamp start_time = 17;
|
||||
}
|
||||
|
||||
// Light variant of ProcessInfo message to help minimize on-disk/on-wire sizes
|
||||
message ProcessInfoLight {
|
||||
// Process ID of the process
|
||||
optional ProcessID id = 1;
|
||||
|
||||
// Process ID of the parent process
|
||||
optional ProcessID parent_id = 2;
|
||||
|
||||
// Original parent ID, remains stable in the event a process is reparented
|
||||
optional int32 original_parent_pid = 3;
|
||||
|
||||
// Process group id the process belongs to
|
||||
optional int32 group_id = 4;
|
||||
|
||||
// Session id the process belongs to
|
||||
optional int32 session_id = 5;
|
||||
|
||||
// Effective user/group info
|
||||
optional UserInfo effective_user = 6;
|
||||
optional GroupInfo effective_group = 7;
|
||||
|
||||
// Real user/group info
|
||||
optional UserInfo real_user = 8;
|
||||
optional GroupInfo real_group = 9;
|
||||
|
||||
// File information for the executable backing this process
|
||||
optional FileInfoLight executable = 10;
|
||||
}
|
||||
|
||||
// Certificate information
|
||||
message CertificateInfo {
|
||||
// Hash of the certificate data
|
||||
optional Hash hash = 1;
|
||||
|
||||
// Common name used in the certificate
|
||||
optional string common_name = 2;
|
||||
}
|
||||
|
||||
// Information about a process execution event
|
||||
message Execution {
|
||||
// The process that executed the new image (e.g. the process that called
|
||||
// `execve(2)` or `posix_spawn(2)``)
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Process info for the newly formed execution
|
||||
optional ProcessInfo target = 2;
|
||||
|
||||
// Script file information
|
||||
// Only valid when a script was executed directly and not as an argument to
|
||||
// an interpreter (e.g. `./foo.sh`, not `/bin/sh ./foo.sh`)
|
||||
optional FileInfo script = 3;
|
||||
|
||||
// The current working directory of the `target` at exec time
|
||||
optional FileInfo working_directory = 4;
|
||||
|
||||
// List of process arguments
|
||||
repeated bytes args = 5;
|
||||
|
||||
// List of environment variables
|
||||
repeated bytes envs = 6;
|
||||
|
||||
// List of file descriptors
|
||||
repeated FileDescriptor fds = 7;
|
||||
|
||||
// Whether or not the list of `fds` is complete or contains partial info
|
||||
optional bool fd_list_truncated = 8;
|
||||
|
||||
// Whether or not the target execution was allowed
|
||||
enum Decision {
|
||||
DECISION_UNKNOWN = 0;
|
||||
DECISION_ALLOW = 1;
|
||||
DECISION_DENY = 2;
|
||||
}
|
||||
optional Decision decision = 9;
|
||||
|
||||
// The policy applied when determining the decision
|
||||
enum Reason {
|
||||
REASON_UNKNOWN = 0;
|
||||
REASON_BINARY = 1;
|
||||
REASON_CERT = 2;
|
||||
REASON_COMPILER = 3;
|
||||
REASON_NOT_RUNNING = 4;
|
||||
REASON_PENDING_TRANSITIVE = 5;
|
||||
REASON_SCOPE = 6;
|
||||
REASON_TEAM_ID = 7;
|
||||
REASON_TRANSITIVE = 8;
|
||||
REASON_LONG_PATH = 9;
|
||||
REASON_NOT_RUNNING = 10;
|
||||
REASON_SIGNING_ID = 11;
|
||||
}
|
||||
optional Reason reason = 10;
|
||||
|
||||
// The mode Santa was in when the decision was applied
|
||||
enum Mode {
|
||||
MODE_UNKNOWN = 0;
|
||||
MODE_LOCKDOWN = 1;
|
||||
MODE_MONITOR = 2;
|
||||
}
|
||||
optional Mode mode = 11;
|
||||
|
||||
optional Decision decision = 1;
|
||||
optional Reason reason = 2;
|
||||
optional string explain = 3;
|
||||
optional string sha256 = 4;
|
||||
optional string cert_sha256 = 5;
|
||||
optional string cert_cn = 6;
|
||||
optional string quarantine_url = 7;
|
||||
optional ProcessInfo process_info = 8;
|
||||
optional Mode mode = 9;
|
||||
optional string path = 10;
|
||||
optional string original_path = 11;
|
||||
repeated string args = 12;
|
||||
optional string machine_id = 13;
|
||||
optional string team_id = 14;
|
||||
// Certificate information for the target executable
|
||||
optional CertificateInfo certificate_info = 12;
|
||||
|
||||
// Additional Santa metadata
|
||||
optional string explain = 13;
|
||||
|
||||
// Information known to LaunchServices about the target executable file
|
||||
optional string quarantine_url = 14;
|
||||
|
||||
// The original path on disk of the target executable
|
||||
// Applies when executables are translocated
|
||||
optional string original_path = 15;
|
||||
}
|
||||
|
||||
message DiskAppeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
optional string fs = 4;
|
||||
optional string model = 5;
|
||||
optional string serial = 6;
|
||||
optional string bus = 7;
|
||||
optional string dmg_path = 8;
|
||||
optional string appearance = 9;
|
||||
// Information about a fork event
|
||||
message Fork {
|
||||
// The forking process
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The newly formed child process
|
||||
optional ProcessInfoLight child = 2;
|
||||
}
|
||||
|
||||
message DiskDisappeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
// Information about an exit event
|
||||
message Exit {
|
||||
// The process that is exiting
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Exit status code information
|
||||
message Exited {
|
||||
optional int32 exit_status = 1;
|
||||
}
|
||||
|
||||
// Signal code
|
||||
message Signaled {
|
||||
optional int32 signal = 1;
|
||||
}
|
||||
|
||||
// Information on how/why the process exited
|
||||
oneof ExitType {
|
||||
Exited exited = 2;
|
||||
Signaled signaled = 3;
|
||||
Signaled stopped = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Information about an open event
|
||||
message Open {
|
||||
// The process that is opening the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being opened
|
||||
optional FileInfo target = 2;
|
||||
|
||||
// Bitmask of flags used to open the file
|
||||
// Note: Represents the mask applied by the kernel, not the typical `open(2)`
|
||||
// flags (e.g. FREAD, FWRITE instead of O_RDONLY, O_RDWR, etc...)
|
||||
optional int32 flags = 3;
|
||||
}
|
||||
|
||||
// Information about a close event
|
||||
message Close {
|
||||
// The process closing the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being closed
|
||||
optional FileInfo target = 2;
|
||||
|
||||
// Whether or not the file was written to
|
||||
optional bool modified = 3;
|
||||
}
|
||||
|
||||
// Information about an exchagedata event
|
||||
// This event is not applicable to all filesystems (notably APFS)
|
||||
message Exchangedata {
|
||||
// The process that is exchanging the data
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// File information for the two files in the exchangedata operation
|
||||
optional FileInfo file1 = 2;
|
||||
optional FileInfo file2 = 3;
|
||||
}
|
||||
|
||||
// Information about a rename event
|
||||
message Rename {
|
||||
// The process renaming the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The source file being renamed
|
||||
optional FileInfo source = 2;
|
||||
|
||||
// The target path when the rename is complete
|
||||
optional string target = 3;
|
||||
|
||||
// Whether or not the target path previously existed
|
||||
optional bool target_existed = 4;
|
||||
}
|
||||
|
||||
// Information about an unlink event
|
||||
message Unlink {
|
||||
// The process deleting the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being deleted
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
// Information about a link event
|
||||
message Link {
|
||||
// The process performing the link
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The source file being linked
|
||||
optional FileInfo source = 2;
|
||||
|
||||
// The path of the new link
|
||||
optional string target = 3;
|
||||
}
|
||||
|
||||
// Information about when disks are added or removed
|
||||
message Disk {
|
||||
// Whether the disk just appeared or disappeared from the system
|
||||
enum Action {
|
||||
ACTION_UNKNOWN = 0;
|
||||
ACTION_APPEARED = 1;
|
||||
ACTION_DISAPPEARED = 2;
|
||||
}
|
||||
optional Action action = 1;
|
||||
|
||||
// Volume path
|
||||
optional string mount = 2;
|
||||
|
||||
// Volume name
|
||||
optional string volume = 3;
|
||||
|
||||
// Media BSD name
|
||||
optional string bsd_name = 4;
|
||||
|
||||
// Kind of volume
|
||||
optional string fs = 5;
|
||||
|
||||
// Device vendor and model information
|
||||
optional string model = 6;
|
||||
|
||||
// Serial number of the device
|
||||
optional string serial = 7;
|
||||
|
||||
// Device protocol
|
||||
optional string bus = 8;
|
||||
|
||||
// Path of the DMG
|
||||
optional string dmg_path = 9;
|
||||
|
||||
// Time device appeared/disappeared
|
||||
optional google.protobuf.Timestamp appearance = 10;
|
||||
}
|
||||
|
||||
// Information emitted when Santa captures bundle information
|
||||
message Bundle {
|
||||
// This is the hash of the file within the bundle that triggered the event
|
||||
optional string sha256 = 1;
|
||||
optional Hash file_hash = 1;
|
||||
|
||||
// This is the hash of the hashes of all executables in the bundle
|
||||
optional string bundle_hash = 2;
|
||||
optional Hash bundle_hash = 2;
|
||||
|
||||
// Name of the bundle
|
||||
optional string bundle_name = 3;
|
||||
|
||||
// Bundle identifier
|
||||
optional string bundle_id = 4;
|
||||
|
||||
// Bundle path
|
||||
optional string bundle_path = 5;
|
||||
|
||||
// Path of the file within the bundle that triggered the event
|
||||
optional string path = 6;
|
||||
}
|
||||
|
||||
message Fork {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
message Exit {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
// Information for a transitive allowlist rule
|
||||
message Allowlist {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional string path = 3;
|
||||
optional string sha256 = 4;
|
||||
// The process that caused the allowlist rule to be generated
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file the new allowlist rule applies to
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
message SantaMessage {
|
||||
google.protobuf.Timestamp event_time = 1;
|
||||
// Information about access to a watched path
|
||||
message FileAccess {
|
||||
// The process that attempted to access the watched path
|
||||
optional ProcessInfo instigator = 1;
|
||||
|
||||
oneof message {
|
||||
FileModification file_modification = 2;
|
||||
Execution execution = 3;
|
||||
DiskAppeared disk_appeared = 4;
|
||||
DiskDisappeared disk_disappeared = 5;
|
||||
Bundle bundle = 6;
|
||||
Fork fork = 7;
|
||||
Exit exit = 8;
|
||||
Allowlist allowlist = 9;
|
||||
// The path that was accessed
|
||||
optional FileInfoLight target = 2;
|
||||
|
||||
// The version of the policy when the decision was made
|
||||
optional string policy_version = 3;
|
||||
|
||||
// The name of the specific policy that triggered this log
|
||||
optional string policy_name = 4;
|
||||
|
||||
// The event type that attempted to access the watched path
|
||||
enum AccessType {
|
||||
ACCESS_TYPE_UNKNOWN = 0;
|
||||
ACCESS_TYPE_OPEN = 1;
|
||||
ACCESS_TYPE_RENAME = 2;
|
||||
ACCESS_TYPE_UNLINK = 3;
|
||||
ACCESS_TYPE_LINK = 4;
|
||||
ACCESS_TYPE_CLONE = 5;
|
||||
ACCESS_TYPE_EXCHANGEDATA = 6;
|
||||
ACCESS_TYPE_COPYFILE = 7;
|
||||
ACCESS_TYPE_CREATE = 8;
|
||||
ACCESS_TYPE_TRUNCATE = 9;
|
||||
}
|
||||
optional AccessType access_type = 5;
|
||||
|
||||
// Whether the operation was allowed or denied and why
|
||||
enum PolicyDecision {
|
||||
POLICY_DECISION_UNKNOWN = 0;
|
||||
POLICY_DECISION_DENIED = 1;
|
||||
POLICY_DECISION_DENIED_INVALID_SIGNATURE = 2;
|
||||
POLICY_DECISION_ALLOWED_AUDIT_ONLY = 3;
|
||||
}
|
||||
optional PolicyDecision policy_decision = 6;
|
||||
}
|
||||
|
||||
// A message encapsulating a single event
|
||||
message SantaMessage {
|
||||
// Machine ID of the host emitting this log
|
||||
// Only valid when EnableMachineIDDecoration configuration option is set
|
||||
optional string machine_id = 1;
|
||||
|
||||
// Timestamp when the event occurred
|
||||
optional google.protobuf.Timestamp event_time = 2;
|
||||
|
||||
// Timestamp when Santa finished processing the event
|
||||
optional google.protobuf.Timestamp processed_time = 3;
|
||||
|
||||
// Event type being described by this message
|
||||
oneof event {
|
||||
Execution execution = 10;
|
||||
Fork fork = 11;
|
||||
Exit exit = 12;
|
||||
Close close = 13;
|
||||
Rename rename = 14;
|
||||
Unlink unlink = 15;
|
||||
Link link = 16;
|
||||
Exchangedata exchangedata = 17;
|
||||
Disk disk = 18;
|
||||
Bundle bundle = 19;
|
||||
Allowlist allowlist = 20;
|
||||
FileAccess file_access = 21;
|
||||
};
|
||||
}
|
||||
|
||||
message SantaMessageBatch {
|
||||
repeated SantaMessage messages = 1;
|
||||
}
|
||||
|
||||
message LogBatch {
|
||||
|
||||
20
Source/common/santa_proto_include_wrapper.h
Normal file
20
Source/common/santa_proto_include_wrapper.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/// 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__COMMON_SANTA_PROTO_INCLUDE_WRAPPER_H
|
||||
#define SANTA__COMMON_SANTA_PROTO_INCLUDE_WRAPPER_H
|
||||
|
||||
#include "Source/common/santa.pb.h"
|
||||
|
||||
#endif
|
||||
@@ -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",
|
||||
@@ -85,7 +103,7 @@ macos_application(
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
63
Source/gui/SNTAboutWindowView.swift
Normal file
63
Source/gui/SNTAboutWindowView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
76
Source/gui/SNTDeviceMessageWindowView.swift
Normal file
76
Source/gui/SNTDeviceMessageWindowView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
- (void)windowDidCloseSilenceHash:(NSString *)hash;
|
||||
@end
|
||||
|
||||
@interface SNTMessageWindowController : NSWindowController
|
||||
@interface SNTMessageWindowController : NSWindowController <NSWindowDelegate>
|
||||
|
||||
- (IBAction)showWindow:(id)sender;
|
||||
- (IBAction)closeWindow:(id)sender;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -92,10 +92,16 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
}
|
||||
|
||||
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
|
||||
NSString *messageHash = [pendingMsg messageHash];
|
||||
// Post a distributed notification, regardless of queue state.
|
||||
[self postDistributedNotification:pendingMsg];
|
||||
|
||||
// If GUI is in silent mode or if there's already a notification queued for
|
||||
// this message, don't do anything else.
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
if ([self notificationAlreadyQueued:pendingMsg]) return;
|
||||
|
||||
// See if this message is silenced.
|
||||
// See if this message has been user-silenced.
|
||||
NSString *messageHash = [pendingMsg messageHash];
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
@@ -114,7 +120,6 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
[self postDistributedNotification:pendingMsg];
|
||||
|
||||
if (!self.currentWindowController) {
|
||||
[self showQueuedWindow];
|
||||
@@ -315,8 +320,6 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
}
|
||||
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
@@ -329,8 +332,6 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
}
|
||||
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
// #import <MOLCertificate/MOLCertificate.h>
|
||||
// #import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
@@ -62,28 +62,24 @@ int main(int argc, const char *argv[]) {
|
||||
sysxOperation = @(2);
|
||||
}
|
||||
if (sysxOperation) {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
NSString *e = [SNTXPCControlInterface systemExtensionID];
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
|
||||
OSSystemExtensionRequest *req;
|
||||
if (sysxOperation.intValue == 1) {
|
||||
NSLog(@"Requesting SystemExtension activation");
|
||||
req = [OSSystemExtensionRequest activationRequestForExtension:e queue:q];
|
||||
} else if (sysxOperation.intValue == 2) {
|
||||
NSLog(@"Requesting SystemExtension deactivation");
|
||||
req = [OSSystemExtensionRequest deactivationRequestForExtension:e queue:q];
|
||||
}
|
||||
if (req) {
|
||||
SNTSystemExtensionDelegate *ed = [[SNTSystemExtensionDelegate alloc] init];
|
||||
req.delegate = ed;
|
||||
[[OSSystemExtensionManager sharedManager] submitRequest:req];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60), q, ^{
|
||||
exit(1);
|
||||
});
|
||||
[[NSRunLoop mainRunLoop] run];
|
||||
}
|
||||
} else {
|
||||
exit(1);
|
||||
NSString *e = [SNTXPCControlInterface systemExtensionID];
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
|
||||
OSSystemExtensionRequest *req;
|
||||
if (sysxOperation.intValue == 1) {
|
||||
NSLog(@"Requesting SystemExtension activation");
|
||||
req = [OSSystemExtensionRequest activationRequestForExtension:e queue:q];
|
||||
} else if (sysxOperation.intValue == 2) {
|
||||
NSLog(@"Requesting SystemExtension deactivation");
|
||||
req = [OSSystemExtensionRequest deactivationRequestForExtension:e queue:q];
|
||||
}
|
||||
if (req) {
|
||||
SNTSystemExtensionDelegate *ed = [[SNTSystemExtensionDelegate alloc] init];
|
||||
req.delegate = ed;
|
||||
[[OSSystemExtensionManager sharedManager] submitRequest:req];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60), q, ^{
|
||||
exit(1);
|
||||
});
|
||||
[[NSRunLoop mainRunLoop] run];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ macos_command_line_application(
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
|
||||
@@ -7,21 +7,44 @@ package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santactl_cmd",
|
||||
srcs = [
|
||||
"SNTCommand.m",
|
||||
"SNTCommandController.m",
|
||||
],
|
||||
hdrs = [
|
||||
"SNTCommand.h",
|
||||
"SNTCommandController.h",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommandPrintLog",
|
||||
srcs = ["Commands/SNTCommandPrintLog.mm"],
|
||||
deps = [
|
||||
":santactl_cmd",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:santa_cc_proto_library_wrapper",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:binaryproto_cc_proto_library_wrapper",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santactl_lib",
|
||||
srcs = [
|
||||
"SNTCommand.h",
|
||||
"SNTCommand.m",
|
||||
"SNTCommandController.h",
|
||||
"SNTCommandController.m",
|
||||
"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": [
|
||||
@@ -33,8 +56,9 @@ objc_library(
|
||||
sdk_dylibs = ["libz"],
|
||||
sdk_frameworks = ["IOKit"],
|
||||
deps = [
|
||||
":SNTCommandPrintLog",
|
||||
":santactl_cmd",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
@@ -66,7 +90,7 @@ macos_command_line_application(
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
|
||||
@@ -40,39 +40,39 @@ 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 {
|
||||
santa_vnode_id_t vnodeID = [self vnodeIDForFile:arguments.firstObject];
|
||||
[[self.daemonConn remoteObjectProxy]
|
||||
SantaVnode vnodeID = [self vnodeIDForFile:arguments.firstObject];
|
||||
[[self.daemonConn synchronousRemoteObjectProxy]
|
||||
checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
LOGI(@"File exists in [allowlist] kernel cache");
|
||||
withReply:^(SNTAction action) {
|
||||
if (action == SNTActionRespondAllow) {
|
||||
LOGI(@"File exists in [allowlist] cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
LOGI(@"File exists in [blocklist] kernel cache");
|
||||
} else if (action == SNTActionRespondDeny) {
|
||||
LOGI(@"File exists in [blocklist] cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_ALLOW_COMPILER) {
|
||||
LOGI(@"File exists in [allowlist compiler] kernel cache");
|
||||
} else if (action == SNTActionRespondAllowCompiler) {
|
||||
LOGI(@"File exists in [allowlist compiler] cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_UNSET) {
|
||||
} else if (action == SNTActionUnset) {
|
||||
LOGE(@"File does not exist in cache");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (santa_vnode_id_t)vnodeIDForFile:(NSString *)path {
|
||||
- (SantaVnode)vnodeIDForFile:(NSString *)path {
|
||||
struct stat fstat = {};
|
||||
stat(path.fileSystemRepresentation, &fstat);
|
||||
santa_vnode_id_t ret = {.fsid = fstat.st_dev, .fileid = fstat.st_ino};
|
||||
SantaVnode ret = {.fsid = fstat.st_dev, .fileid = fstat.st_ino};
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -68,14 +68,34 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
|
||||
for (NSString *fieldName in metric[@"fields"]) {
|
||||
for (NSDictionary *field in metric[@"fields"][fieldName]) {
|
||||
const char *fieldNameStr = [fieldName cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
const char *fieldValueStr = [field[@"value"] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
const char *createdStr = [field[@"created"] UTF8String];
|
||||
const char *lastUpdatedStr = [field[@"last_updated"] UTF8String];
|
||||
const char *data = [[NSString stringWithFormat:@"%@", field[@"data"]] UTF8String];
|
||||
|
||||
if (strlen(fieldNameStr) > 0) {
|
||||
printf(" %-25s | %s=%s\n", "Field", fieldNameStr, fieldValueStr);
|
||||
NSArray<NSString *> *fields = [fieldName componentsSeparatedByString:@","];
|
||||
NSArray<NSString *> *fieldValues = [field[@"value"] componentsSeparatedByString:@","];
|
||||
|
||||
if (fields.count != fieldValues.count) {
|
||||
fprintf(stderr, "metric %s has a different number of field names and field values",
|
||||
[fieldName UTF8String]);
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *fieldDisplayString = @"";
|
||||
|
||||
if (fields.count >= 1 && fields[0].length) {
|
||||
for (int i = 0; i < fields.count; i++) {
|
||||
fieldDisplayString = [fieldDisplayString
|
||||
stringByAppendingString:[NSString
|
||||
stringWithFormat:@"%@=%@", fields[i], fieldValues[i]]];
|
||||
if (i < fields.count - 1) {
|
||||
fieldDisplayString = [fieldDisplayString stringByAppendingString:@","];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (![fieldDisplayString isEqualToString:@""]) {
|
||||
printf(" %-25s | %s\n", "Field", [fieldDisplayString UTF8String]);
|
||||
}
|
||||
|
||||
printf(" %-25s | %s\n", "Created", createdStr);
|
||||
@@ -146,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"]];
|
||||
|
||||
131
Source/santactl/Commands/SNTCommandPrintLog.mm
Normal file
131
Source/santactl/Commands/SNTCommandPrintLog.mm
Normal file
@@ -0,0 +1,131 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <google/protobuf/util/json_util.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/common/santa_proto_include_wrapper.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
#include "Source/santad/Logs/EndpointSecurity/Writers/FSSpool/binaryproto_proto_include_wrapper.h"
|
||||
#include "google/protobuf/any.pb.h"
|
||||
|
||||
using google::protobuf::util::JsonPrintOptions;
|
||||
using google::protobuf::util::MessageToJsonString;
|
||||
using santa::fsspool::binaryproto::LogBatch;
|
||||
namespace pbv1 = ::santa::pb::v1;
|
||||
|
||||
@interface SNTCommandPrintLog : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandPrintLog
|
||||
|
||||
REGISTER_COMMAND_NAME(@"printlog")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Prints the contents of Santa protobuf log files as JSON.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return @"Prints the contents of serialized Santa protobuf logs as JSON.\n"
|
||||
@"Multiple paths can be provided. The output is a list of all the \n"
|
||||
@"SantaMessage entries per-file. E.g.: \n"
|
||||
@" [\n"
|
||||
@" [\n"
|
||||
@" ... file 1 contents ...\n"
|
||||
@" ],\n"
|
||||
@" [\n"
|
||||
@" ... file N contents ...\n"
|
||||
@" ]\n"
|
||||
@" ]";
|
||||
}
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
JsonPrintOptions options;
|
||||
options.always_print_enums_as_ints = false;
|
||||
options.always_print_primitive_fields = true;
|
||||
options.preserve_proto_field_names = true;
|
||||
options.add_whitespace = true;
|
||||
|
||||
for (int argIdx = 0; argIdx < [arguments count]; argIdx++) {
|
||||
NSString *path = arguments[argIdx];
|
||||
int fd = open([path UTF8String], O_RDONLY);
|
||||
if (fd == -1) {
|
||||
LOGE(@"Failed to open '%@': errno: %d: %s", path, errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
LogBatch logBatch;
|
||||
bool ret = logBatch.ParseFromFileDescriptor(fd);
|
||||
close(fd);
|
||||
|
||||
if (!ret) {
|
||||
LOGE(@"Failed to parse '%@'", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argIdx != 0) {
|
||||
std::cout << ",";
|
||||
} else {
|
||||
// Print the opening outer JSON array
|
||||
std::cout << "[";
|
||||
}
|
||||
std::cout << "\n[\n";
|
||||
|
||||
int numRecords = logBatch.records_size();
|
||||
|
||||
for (int i = 0; i < numRecords; i++) {
|
||||
const google::protobuf::Any &any = logBatch.records(i);
|
||||
::pbv1::SantaMessage santaMsg;
|
||||
if (!any.UnpackTo(&santaMsg)) {
|
||||
LOGE(@"Failed to unpack Any proto to SantaMessage in file '%@'", path);
|
||||
break;
|
||||
}
|
||||
|
||||
if (i != 0) {
|
||||
std::cout << ",\n";
|
||||
}
|
||||
|
||||
std::string json;
|
||||
if (!MessageToJsonString(santaMsg, &json, options).ok()) {
|
||||
LOGE(@"Unable to convert message to JSON in file: '%@'", path);
|
||||
}
|
||||
std::cout << json;
|
||||
}
|
||||
|
||||
std::cout << "]" << std::flush;
|
||||
|
||||
if (argIdx == ([arguments count] - 1)) {
|
||||
// Print the closing outer JSON array
|
||||
std::cout << "]\n";
|
||||
}
|
||||
}
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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);
|
||||
|
||||
@@ -46,116 +46,116 @@ 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;
|
||||
__block uint64_t watchItemsRuleCount = 0;
|
||||
__block NSString *watchItemsPolicyVersion = nil;
|
||||
__block NSString *watchItemsConfigPath = nil;
|
||||
__block NSTimeInterval watchItemsLastUpdateEpoch = 0;
|
||||
[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.
|
||||
@@ -170,6 +170,10 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
NSString *ruleSyncLastSuccessStr =
|
||||
[dateFormatter stringFromDate:ruleSyncLastSuccess] ?: fullSyncLastSuccessStr;
|
||||
|
||||
NSString *watchItemsLastUpdateStr =
|
||||
[dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:watchItemsLastUpdateEpoch]]
|
||||
?: @"Never";
|
||||
|
||||
NSString *syncURLStr = configurator.syncBaseURL.absoluteString;
|
||||
|
||||
BOOL exportMetrics = configurator.exportMetrics;
|
||||
@@ -181,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),
|
||||
@@ -194,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),
|
||||
@@ -212,6 +219,22 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
},
|
||||
} mutableCopy];
|
||||
|
||||
NSDictionary *watchItems;
|
||||
if (watchItemsEnabled) {
|
||||
watchItems = @{
|
||||
@"enabled" : @(watchItemsEnabled),
|
||||
@"rule_count" : @(watchItemsRuleCount),
|
||||
@"policy_version" : watchItemsPolicyVersion,
|
||||
@"config_path" : watchItemsConfigPath ?: @"null",
|
||||
@"last_policy_update" : watchItemsLastUpdateStr ?: @"null",
|
||||
};
|
||||
} else {
|
||||
watchItems = @{
|
||||
@"enabled" : @(watchItemsEnabled),
|
||||
};
|
||||
}
|
||||
stats[@"watch_items"] = watchItems;
|
||||
|
||||
stats[@"cache"] = @{
|
||||
@"root_cache_count" : @(rootCacheCount),
|
||||
@"non_root_cache_count" : @(nonRootCacheCount),
|
||||
@@ -225,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) {
|
||||
@@ -242,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);
|
||||
@@ -251,6 +276,15 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf(" %-25s | %lld\n", "Rules", staticRuleCount);
|
||||
}
|
||||
|
||||
printf(">>> Watch Items\n");
|
||||
printf(" %-25s | %s\n", "Enabled", (watchItemsEnabled ? "Yes" : "No"));
|
||||
if (watchItemsEnabled) {
|
||||
printf(" %-25s | %s\n", "Policy Version", watchItemsPolicyVersion.UTF8String);
|
||||
printf(" %-25s | %llu\n", "Rule Count", watchItemsRuleCount);
|
||||
printf(" %-25s | %s\n", "Config Path", (watchItemsConfigPath ?: @"(embedded)").UTF8String);
|
||||
printf(" %-25s | %s\n", "Last Policy Update", watchItemsLastUpdateStr.UTF8String);
|
||||
}
|
||||
|
||||
if (syncURLStr) {
|
||||
printf(">>> Sync Info\n");
|
||||
printf(" %-25s | %s\n", "Sync Server", [syncURLStr UTF8String]);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
@@ -38,18 +38,18 @@
|
||||
"type" : 9,
|
||||
"description" : "Count of process exec events on the host",
|
||||
"fields" : {
|
||||
"rule_type" : [
|
||||
"rule_type,client" : [
|
||||
{
|
||||
"created" : "2021-09-16T21:07:34.826Z",
|
||||
"last_updated" : "2021-09-16T21:07:34.826Z",
|
||||
"value" : "binary",
|
||||
"data" : 1
|
||||
"value" : "certificate,authorizer",
|
||||
"data" : 2
|
||||
},
|
||||
{
|
||||
"created" : "2021-09-16T21:07:34.826Z",
|
||||
"last_updated" : "2021-09-16T21:07:34.826Z",
|
||||
"value" : "certificate",
|
||||
"data" : 2
|
||||
"value" : "binary,authorizer",
|
||||
"data" : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
Metric Name | /santa/events
|
||||
Description | Count of process exec events on the host
|
||||
Type | SNTMetricTypeCounter
|
||||
Field | rule_type=binary
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 1
|
||||
Field | rule_type=certificate
|
||||
Field | rule_type=certificate,client=authorizer
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 2
|
||||
Field | rule_type=binary,client=authorizer
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 1
|
||||
|
||||
Metric Name | /santa/using_endpoint_security_framework
|
||||
Description | Is santad using the endpoint security framework
|
||||
|
||||
@@ -23,6 +23,7 @@ objc_library(
|
||||
hdrs = ["DataLayer/SNTRuleTable.h"],
|
||||
deps = [
|
||||
":SNTDatabaseTable",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -34,6 +35,38 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "WatchItemPolicy",
|
||||
hdrs = ["DataLayer/WatchItemPolicy.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "WatchItems",
|
||||
srcs = ["DataLayer/WatchItems.mm"],
|
||||
hdrs = ["DataLayer/WatchItems.h"],
|
||||
deps = [
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:String",
|
||||
"//Source/common:Unit",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "WatchItemsTest",
|
||||
srcs = ["DataLayer/WatchItemsTest.mm"],
|
||||
deps = [
|
||||
":WatchItemPolicy",
|
||||
":WatchItems",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:TestUtils",
|
||||
"//Source/common:Unit",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTEventTable",
|
||||
srcs = ["DataLayer/SNTEventTable.m"],
|
||||
@@ -62,19 +95,21 @@ objc_library(
|
||||
hdrs = ["EventProviders/SNTEndpointSecurityEventHandler.h"],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
"//Source/common:SNTCommon",
|
||||
":Metrics",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTApplicationCoreMetrics",
|
||||
srcs = ["SNTApplicationCoreMetrics.m"],
|
||||
srcs = ["SNTApplicationCoreMetrics.mm"],
|
||||
hdrs = ["SNTApplicationCoreMetrics.h"],
|
||||
deps = [
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTSystemInfo",
|
||||
"//Source/common:SystemResources",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -103,6 +138,9 @@ objc_library(
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:SantaVnodeHash",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -117,7 +155,6 @@ objc_library(
|
||||
":SNTDecisionCache",
|
||||
":SNTRuleTable",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -160,7 +197,6 @@ objc_library(
|
||||
hdrs = ["SNTPolicyProcessor.h"],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileInfo",
|
||||
@@ -185,9 +221,9 @@ objc_library(
|
||||
":SNTPolicyProcessor",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
"//Source/common:BranchPrediction",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
@@ -196,6 +232,8 @@ objc_library(
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:String",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
@@ -208,6 +246,8 @@ objc_library(
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -220,10 +260,13 @@ objc_library(
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":SNTEndpointSecurityClientBase",
|
||||
"//Source/common:SNTCommon",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:BranchPrediction",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SystemResources",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -238,11 +281,13 @@ objc_library(
|
||||
":EndpointSecurityEnricher",
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:Unit",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -254,8 +299,10 @@ objc_library(
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:SNTLogging",
|
||||
],
|
||||
)
|
||||
@@ -270,14 +317,47 @@ objc_library(
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityEnricher",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":SNTExecutionController",
|
||||
"//Source/common:BranchPrediction",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTLogging",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTEndpointSecurityFileAccessAuthorizer",
|
||||
srcs = ["EventProviders/SNTEndpointSecurityFileAccessAuthorizer.mm"],
|
||||
hdrs = ["EventProviders/SNTEndpointSecurityFileAccessAuthorizer.h"],
|
||||
deps = [
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityEnricher",
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":RateLimiter",
|
||||
":SNTDecisionCache",
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":WatchItemPolicy",
|
||||
":WatchItems",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTStrengthify",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:SantaVnodeHash",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTEndpointSecurityDeviceManager",
|
||||
srcs = ["EventProviders/SNTEndpointSecurityDeviceManager.mm"],
|
||||
@@ -287,6 +367,7 @@ objc_library(
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
@@ -301,9 +382,23 @@ objc_library(
|
||||
deps = [
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityClient",
|
||||
"//Source/common:SNTCommon",
|
||||
"//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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -334,6 +429,26 @@ objc_library(
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "EndpointSecuritySerializerUtilities",
|
||||
srcs = ["Logs/EndpointSecurity/Serializers/Utilities.mm"],
|
||||
hdrs = ["Logs/EndpointSecurity/Serializers/Utilities.h"],
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:SantaVnodeHash",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -343,6 +458,7 @@ objc_library(
|
||||
hdrs = ["Logs/EndpointSecurity/Serializers/Empty.h"],
|
||||
deps = [
|
||||
":EndpointSecuritySerializer",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -355,6 +471,7 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
"//Source/common:String",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -366,11 +483,29 @@ objc_library(
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecuritySanitizableString",
|
||||
":EndpointSecuritySerializer",
|
||||
":EndpointSecuritySerializerUtilities",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "EndpointSecuritySerializerProtobuf",
|
||||
srcs = ["Logs/EndpointSecurity/Serializers/Protobuf.mm"],
|
||||
hdrs = ["Logs/EndpointSecurity/Serializers/Protobuf.h"],
|
||||
deps = [
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecuritySerializer",
|
||||
":EndpointSecuritySerializerUtilities",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:String",
|
||||
"//Source/common:santa_cc_proto_library_wrapper",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -394,6 +529,21 @@ objc_library(
|
||||
hdrs = ["Logs/EndpointSecurity/Writers/File.h"],
|
||||
deps = [
|
||||
":EndpointSecurityWriter",
|
||||
"//Source/common:BranchPrediction",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "EndpointSecurityWriterSpool",
|
||||
srcs = ["Logs/EndpointSecurity/Writers/Spool.mm"],
|
||||
hdrs = ["Logs/EndpointSecurity/Writers/Spool.h"],
|
||||
deps = [
|
||||
":EndpointSecurityWriter",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:santa_cc_proto_library_wrapper",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool_log_batch_writer",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -417,10 +567,13 @@ objc_library(
|
||||
":EndpointSecuritySerializer",
|
||||
":EndpointSecuritySerializerBasicString",
|
||||
":EndpointSecuritySerializerEmpty",
|
||||
":EndpointSecuritySerializerProtobuf",
|
||||
":EndpointSecurityWriter",
|
||||
":EndpointSecurityWriterFile",
|
||||
":EndpointSecurityWriterNull",
|
||||
":EndpointSecurityWriterSpool",
|
||||
":EndpointSecurityWriterSyslog",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
@@ -436,6 +589,7 @@ objc_library(
|
||||
hdrs = ["EventProviders/EndpointSecurity/Message.h"],
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -451,6 +605,8 @@ objc_library(
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:Platform",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -467,6 +623,7 @@ objc_library(
|
||||
":SNTPolicyProcessor",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
":WatchItems",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -508,20 +665,24 @@ objc_library(
|
||||
":Metrics",
|
||||
":SNTCompilerController",
|
||||
":SNTDaemonControlController",
|
||||
":SNTDecisionCache",
|
||||
":SNTEndpointSecurityAuthorizer",
|
||||
":SNTEndpointSecurityDeviceManager",
|
||||
":SNTEndpointSecurityFileAccessAuthorizer",
|
||||
":SNTEndpointSecurityRecorder",
|
||||
":SNTEndpointSecurityTamperResistance",
|
||||
":SNTExecutionController",
|
||||
":SNTNotificationQueue",
|
||||
":SNTSyncdQueue",
|
||||
":WatchItems",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTKVOManager",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"//Source/common:SNTXPCSyncServiceInterface",
|
||||
"//Source/common:Unit",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
@@ -538,15 +699,20 @@ objc_library(
|
||||
":Metrics",
|
||||
":SNTCompilerController",
|
||||
":SNTDatabaseController",
|
||||
":SNTDecisionCache",
|
||||
":SNTEventTable",
|
||||
":SNTExecutionController",
|
||||
":SNTNotificationQueue",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
":WatchItems",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCUnprivilegedControlInterface",
|
||||
"//Source/common:Unit",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
@@ -567,7 +733,9 @@ objc_library(
|
||||
":SantadDeps",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SystemResources",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -587,7 +755,7 @@ macos_bundle(
|
||||
}),
|
||||
infoplists = ["Info.plist"],
|
||||
linkopts = ["-execute"],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:daemon_dev",
|
||||
@@ -612,6 +780,18 @@ objc_library(
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":WatchItemPolicy",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "MockLogger",
|
||||
testonly = 1,
|
||||
hdrs = ["Logs/EndpointSecurity/MockLogger.h"],
|
||||
deps = [
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
@@ -648,6 +828,7 @@ santa_unit_test(
|
||||
"EndpointSecurity",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -666,7 +847,7 @@ santa_unit_test(
|
||||
data = [
|
||||
"//Source/santad/testdata:binaryrules_testdata",
|
||||
],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "11.0",
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
"EndpointSecurity",
|
||||
@@ -677,6 +858,7 @@ santa_unit_test(
|
||||
tags = ["exclusive"],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTDatabaseController",
|
||||
":SNTEndpointSecurityAuthorizer",
|
||||
@@ -693,9 +875,9 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTApplicationCoreMetricsTest",
|
||||
srcs = [
|
||||
"SNTApplicationCoreMetricsTest.m",
|
||||
"SNTApplicationCoreMetricsTest.mm",
|
||||
],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "11.0",
|
||||
deps = [
|
||||
":SNTApplicationCoreMetrics",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
@@ -720,6 +902,19 @@ santa_unit_test(
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "EndpointSecuritySerializerUtilitiesTest",
|
||||
srcs = ["Logs/EndpointSecurity/Serializers/UtilitiesTest.mm"],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
":EndpointSecuritySerializerUtilities",
|
||||
":MockEndpointSecurityAPI",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "EndpointSecuritySerializerBasicStringTest",
|
||||
srcs = ["Logs/EndpointSecurity/Serializers/BasicStringTest.mm"],
|
||||
@@ -745,6 +940,31 @@ santa_unit_test(
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "EndpointSecuritySerializerProtobufTest",
|
||||
srcs = ["Logs/EndpointSecurity/Serializers/ProtobufTest.mm"],
|
||||
data = [
|
||||
"//Source/santad/testdata:protobuf_json_testdata",
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityEnricher",
|
||||
":EndpointSecurityMessage",
|
||||
":EndpointSecuritySerializer",
|
||||
":EndpointSecuritySerializerProtobuf",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:TestUtils",
|
||||
"//Source/common:santa_cc_proto_library_wrapper",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "AuthResultCacheTest",
|
||||
srcs = ["EventProviders/AuthResultCacheTest.mm"],
|
||||
@@ -754,13 +974,23 @@ santa_unit_test(
|
||||
deps = [
|
||||
":AuthResultCache",
|
||||
":MockEndpointSecurityAPI",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
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"],
|
||||
@@ -785,6 +1015,17 @@ santa_unit_test(
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "EndpointSecurityWriterSpoolTest",
|
||||
srcs = ["Logs/EndpointSecurity/Writers/SpoolTest.mm"],
|
||||
deps = [
|
||||
":EndpointSecurityWriterSpool",
|
||||
"//Source/common:TestUtils",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool_log_batch_writer",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "EndpointSecurityLoggerTest",
|
||||
srcs = ["Logs/EndpointSecurity/LoggerTest.mm"],
|
||||
@@ -799,9 +1040,11 @@ santa_unit_test(
|
||||
":EndpointSecuritySerializer",
|
||||
":EndpointSecuritySerializerBasicString",
|
||||
":EndpointSecuritySerializerEmpty",
|
||||
":EndpointSecuritySerializerProtobuf",
|
||||
":EndpointSecurityWriter",
|
||||
":EndpointSecurityWriterFile",
|
||||
":EndpointSecurityWriterNull",
|
||||
":EndpointSecurityWriterSpool",
|
||||
":EndpointSecurityWriterSyslog",
|
||||
":MockEndpointSecurityAPI",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
@@ -854,6 +1097,8 @@ santa_unit_test(
|
||||
srcs = ["MetricsTest.mm"],
|
||||
deps = [
|
||||
":Metrics",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
@@ -869,7 +1114,6 @@ santa_unit_test(
|
||||
":SNTDecisionCache",
|
||||
":SNTRuleTable",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:TestUtils",
|
||||
@@ -888,8 +1132,11 @@ santa_unit_test(
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTEndpointSecurityClient",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
@@ -912,7 +1159,6 @@ santa_unit_test(
|
||||
":SNTExecutionController",
|
||||
":SNTRuleTable",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileInfo",
|
||||
@@ -935,16 +1181,44 @@ santa_unit_test(
|
||||
":AuthResultCache",
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityAuthorizer",
|
||||
":SNTExecutionController",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTEndpointSecurityFileAccessAuthorizerTest",
|
||||
srcs = ["EventProviders/SNTEndpointSecurityFileAccessAuthorizerTest.mm"],
|
||||
sdk_dylibs = [
|
||||
"EndpointSecurity",
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
":MockEndpointSecurityAPI",
|
||||
":MockLogger",
|
||||
":SNTDecisionCache",
|
||||
":SNTEndpointSecurityFileAccessAuthorizer",
|
||||
":WatchItemPolicy",
|
||||
":WatchItems",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:TestUtils",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTEndpointSecurityTamperResistanceTest",
|
||||
srcs = ["EventProviders/SNTEndpointSecurityTamperResistanceTest.mm"],
|
||||
@@ -954,8 +1228,10 @@ santa_unit_test(
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTEndpointSecurityTamperResistance",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
@@ -976,10 +1252,13 @@ santa_unit_test(
|
||||
":EndpointSecurityEnricher",
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityRecorder",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:TestUtils",
|
||||
"//Source/common:Unit",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
@@ -997,6 +1276,7 @@ santa_unit_test(
|
||||
":DiskArbitrationTestLib",
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTEndpointSecurityDeviceManager",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -1038,20 +1318,27 @@ test_suite(
|
||||
":EndpointSecuritySanitizableStringTest",
|
||||
":EndpointSecuritySerializerBasicStringTest",
|
||||
":EndpointSecuritySerializerEmptyTest",
|
||||
":EndpointSecuritySerializerProtobufTest",
|
||||
":EndpointSecuritySerializerUtilitiesTest",
|
||||
":EndpointSecurityWriterFileTest",
|
||||
":EndpointSecurityWriterSpoolTest",
|
||||
":MetricsTest",
|
||||
":RateLimiterTest",
|
||||
":SNTApplicationCoreMetricsTest",
|
||||
":SNTCompilerControllerTest",
|
||||
":SNTDecisionCacheTest",
|
||||
":SNTEndpointSecurityAuthorizerTest",
|
||||
":SNTEndpointSecurityClientTest",
|
||||
":SNTEndpointSecurityDeviceManagerTest",
|
||||
":SNTEndpointSecurityFileAccessAuthorizerTest",
|
||||
":SNTEndpointSecurityRecorderTest",
|
||||
":SNTEndpointSecurityTamperResistanceTest",
|
||||
":SNTEventTableTest",
|
||||
":SNTExecutionControllerTest",
|
||||
":SNTRuleTableTest",
|
||||
":SantadTest",
|
||||
":WatchItemsTest",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool_test",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "Source/common/Platform.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
#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;
|
||||
@@ -36,7 +39,7 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
// instead we use the following preprocessor macros to conditionally compile these API. The
|
||||
// drawback here is that if a pre-macOS 12 SDK is used to build Santa and it is then deployed
|
||||
// on macOS 12 or later, the dynamic mute set will not be computed.
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
#if HAVE_MACOS_12
|
||||
// Create a temporary ES client in order to grab the default set of muted paths.
|
||||
// TODO(mlw): Reorganize this code so that a temporary ES client doesn't need to be created
|
||||
es_client_t *client = NULL;
|
||||
@@ -172,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];
|
||||
@@ -271,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"]
|
||||
@@ -280,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;
|
||||
@@ -287,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.
|
||||
@@ -300,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
|
||||
@@ -315,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];
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
107
Source/santad/DataLayer/WatchItemPolicy.h
Normal file
107
Source/santad/DataLayer/WatchItemPolicy.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/// 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__DATALAYER_WATCHITEMPOLICY_H
|
||||
#define SANTA__SANTAD__DATALAYER_WATCHITEMPOLICY_H
|
||||
|
||||
#include <Kernel/kern/cs_blobs.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace santa::santad::data_layer {
|
||||
|
||||
enum class WatchItemPathType {
|
||||
kPrefix,
|
||||
kLiteral,
|
||||
};
|
||||
|
||||
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::optional<bool> pb)
|
||||
: binary_path(bp),
|
||||
signing_id(sid),
|
||||
team_id(ti),
|
||||
cdhash(std::move(cdh)),
|
||||
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 &&
|
||||
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); }
|
||||
|
||||
std::string binary_path;
|
||||
std::string signing_id;
|
||||
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 &&
|
||||
invert_process_exceptions == other.invert_process_exceptions &&
|
||||
processes == other.processes;
|
||||
}
|
||||
|
||||
bool operator!=(const WatchItemPolicy &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
std::string name;
|
||||
std::string path;
|
||||
WatchItemPathType path_type;
|
||||
bool allow_read_access;
|
||||
bool audit_only;
|
||||
bool invert_process_exceptions;
|
||||
std::vector<Process> processes;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
|
||||
#endif
|
||||
132
Source/santad/DataLayer/WatchItems.h
Normal file
132
Source/santad/DataLayer/WatchItems.h
Normal file
@@ -0,0 +1,132 @@
|
||||
/// 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__DATALAYER_WATCHITEMS_H
|
||||
#define SANTA__SANTAD__DATALAYER_WATCHITEMS_H
|
||||
|
||||
#include <CommonCrypto/CommonDigest.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/common/PrefixTree.h"
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityEventHandler.h"
|
||||
|
||||
extern NSString *const kWatchItemConfigKeyVersion;
|
||||
extern NSString *const kWatchItemConfigKeyWatchItems;
|
||||
extern NSString *const kWatchItemConfigKeyPaths;
|
||||
extern NSString *const kWatchItemConfigKeyPathsPath;
|
||||
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 {
|
||||
class WatchItemsPeer;
|
||||
}
|
||||
|
||||
namespace santa::santad::data_layer {
|
||||
|
||||
struct WatchItemsState {
|
||||
uint64_t rule_count;
|
||||
NSString *policy_version;
|
||||
NSString *config_path;
|
||||
NSTimeInterval last_config_load_epoch;
|
||||
};
|
||||
|
||||
class WatchItems : public std::enable_shared_from_this<WatchItems> {
|
||||
public:
|
||||
using VersionAndPolicies =
|
||||
std::pair<std::string, std::vector<std::optional<std::shared_ptr<WatchItemPolicy>>>>;
|
||||
using WatchItemsTree = santa::common::PrefixTree<std::shared_ptr<WatchItemPolicy>>;
|
||||
|
||||
// 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,
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
void UpdateCurrentState(std::unique_ptr<WatchItemsTree> new_tree,
|
||||
std::set<std::pair<std::string, WatchItemPathType>> &&new_monitored_paths,
|
||||
NSDictionary *new_config);
|
||||
bool BuildPolicyTree(const std::vector<std::shared_ptr<WatchItemPolicy>> &watch_items,
|
||||
WatchItemsTree &tree,
|
||||
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);
|
||||
|
||||
absl::Mutex lock_;
|
||||
|
||||
std::unique_ptr<WatchItemsTree> watch_items_ ABSL_GUARDED_BY(lock_);
|
||||
NSDictionary *current_config_ ABSL_GUARDED_BY(lock_);
|
||||
NSTimeInterval last_update_time_ ABSL_GUARDED_BY(lock_);
|
||||
std::set<std::pair<std::string, WatchItemPathType>> currently_monitored_paths_
|
||||
ABSL_GUARDED_BY(lock_);
|
||||
std::string policy_version_ ABSL_GUARDED_BY(lock_);
|
||||
std::set<id<SNTEndpointSecurityDynamicEventHandler>> registerd_clients_ ABSL_GUARDED_BY(lock_);
|
||||
bool periodic_task_started_ = false;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
|
||||
#endif
|
||||
797
Source/santad/DataLayer/WatchItems.mm
Normal file
797
Source/santad/DataLayer/WatchItems.mm
Normal file
@@ -0,0 +1,797 @@
|
||||
/// 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/DataLayer/WatchItems.h"
|
||||
|
||||
#include <CommonCrypto/CommonDigest.h>
|
||||
#include <Kernel/kern/cs_blobs.h>
|
||||
#include <ctype.h>
|
||||
#include <glob.h>
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#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;
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
|
||||
NSString *const kWatchItemConfigKeyVersion = @"Version";
|
||||
NSString *const kWatchItemConfigKeyWatchItems = @"WatchItems";
|
||||
NSString *const kWatchItemConfigKeyPaths = @"Paths";
|
||||
NSString *const kWatchItemConfigKeyPathsPath = @"Path";
|
||||
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;
|
||||
|
||||
// Semi-arbitrary upper bound.
|
||||
static constexpr NSUInteger kMaxSigningIDLength = 512;
|
||||
|
||||
// Semi-arbitrary minimum allowed reapplication frequency.
|
||||
// Goal is to prevent a configuration setting that would cause too much
|
||||
// churn rebuilding glob paths based on the state of the filesystem.
|
||||
static constexpr uint64_t kMinReapplyConfigFrequencySecs = 15;
|
||||
|
||||
namespace santa::santad::data_layer {
|
||||
|
||||
// Type aliases
|
||||
using ValidatorBlock = bool (^)(id, NSError **);
|
||||
using PathAndTypePair = std::pair<std::string, WatchItemPathType>;
|
||||
using PathList = std::vector<PathAndTypePair>;
|
||||
using ProcessList = std::vector<WatchItemPolicy::Process>;
|
||||
|
||||
static void PopulateError(NSError **err, NSString *msg) {
|
||||
if (err) {
|
||||
*err = [NSError errorWithDomain:@"com.google.santa.watchitems"
|
||||
code:0
|
||||
userInfo:@{NSLocalizedDescriptionKey : msg}];
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the given string has the expected length and only
|
||||
/// contains valid hex digits
|
||||
bool ConfirmValidHexString(NSString *str, size_t expected_length) {
|
||||
if (str.length != expected_length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
if (!isxdigit([str characterAtIndex:i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> HexStringToBytes(NSString *str) {
|
||||
if (!str) {
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
bytes.reserve(str.length / 2);
|
||||
|
||||
char cur_byte[3];
|
||||
cur_byte[2] = '\0';
|
||||
|
||||
for (int i = 0; i < str.length / 2; i++) {
|
||||
cur_byte[0] = [str characterAtIndex:(i * 2)];
|
||||
cur_byte[1] = [str characterAtIndex:(i * 2 + 1)];
|
||||
|
||||
bytes.push_back(std::strtoul(cur_byte, nullptr, 16));
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// Given a length, returns a ValidatorBlock that confirms the
|
||||
// string is a valid hex string of the given length.
|
||||
ValidatorBlock HexValidator(NSUInteger expected_length) {
|
||||
return ^bool(NSString *val, NSError **err) {
|
||||
if (!ConfirmValidHexString(val, expected_length)) {
|
||||
PopulateError(
|
||||
err, [NSString stringWithFormat:@"Expected hex string of length %lu", expected_length]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
// Given a max length, returns a ValidatorBlock that confirms the
|
||||
// string is a not longer than the max.
|
||||
ValidatorBlock LenRangeValidator(NSUInteger min_length, NSUInteger max_length) {
|
||||
return ^bool(NSString *val, NSError **err) {
|
||||
if (val.length < min_length) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Value too short. Got: %lu, Min: %lu",
|
||||
val.length, min_length]);
|
||||
return false;
|
||||
} else if (val.length > max_length) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Value too long. Got: %lu, Max: %lu",
|
||||
val.length, max_length]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/// Ensure the key exists (if required) and the value matches the expected type
|
||||
bool VerifyConfigKey(NSDictionary *dict, const NSString *key, Class expected, NSError **err,
|
||||
bool required = false, bool (^Validator)(id, NSError **) = nil) {
|
||||
if (dict[key]) {
|
||||
if (![dict[key] isKindOfClass:expected]) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Expected type '%@' for key '%@' (got: %@)",
|
||||
NSStringFromClass(expected), key,
|
||||
NSStringFromClass([dict[key] class])]);
|
||||
return false;
|
||||
}
|
||||
|
||||
NSError *validator_err;
|
||||
if (Validator && !Validator(dict[key], &validator_err)) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Invalid content in key '%@': %@", key,
|
||||
validator_err.localizedDescription]);
|
||||
return false;
|
||||
}
|
||||
} else if (required) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Missing required key '%@'", key]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Ensure all values of the array key in the dictionary conform to the
|
||||
/// expected type. If a Validator block is supplied, each item is also
|
||||
/// subject to the custom validation method.
|
||||
bool VerifyConfigKeyArray(NSDictionary *dict, NSString *key, Class expected, NSError **err,
|
||||
bool (^Validator)(id, NSError **) = nil) {
|
||||
if (!VerifyConfigKey(dict, key, [NSArray class], err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
__block bool success = true;
|
||||
__block NSError *block_err;
|
||||
|
||||
[dict[key] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (![obj isKindOfClass:expected]) {
|
||||
success = false;
|
||||
PopulateError(&block_err,
|
||||
[NSString stringWithFormat:@"Expected all '%@' types in array key '%@'",
|
||||
NSStringFromClass(expected), key]);
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *validator_err;
|
||||
if (Validator && !Validator(obj, &validator_err)) {
|
||||
PopulateError(&block_err,
|
||||
[NSString stringWithFormat:@"Invalid content in array key '%@': %@", key,
|
||||
validator_err.localizedDescription]);
|
||||
success = false;
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
}];
|
||||
|
||||
if (!success && block_err) {
|
||||
PopulateError(err, block_err.localizedDescription);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// The `Paths` array can contain only `string` and `dict` types:
|
||||
/// - For `string` types, the default path type `kDefaultPathType` is used
|
||||
/// - For `dict` types, there is a required `Path` key. and an optional
|
||||
/// `IsPrefix` key to set the path type to something other than the default
|
||||
///
|
||||
/// Example:
|
||||
/// <array>
|
||||
/// <string>/my/path</string>
|
||||
/// <dict>
|
||||
/// <key>Path</key>
|
||||
/// <string>/another/partial/path</string>
|
||||
/// <key>IsPrefix</key>
|
||||
/// <true/>
|
||||
/// </dict>
|
||||
/// </array>
|
||||
std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSError **err) {
|
||||
PathList path_list;
|
||||
|
||||
for (id path in paths) {
|
||||
if ([path isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *path_dict = (NSDictionary *)path;
|
||||
if (!VerifyConfigKey(path_dict, kWatchItemConfigKeyPathsPath, [NSString class], err, true,
|
||||
LenRangeValidator(1, PATH_MAX))) {
|
||||
return Unit{};
|
||||
}
|
||||
|
||||
NSString *path_str = path_dict[kWatchItemConfigKeyPathsPath];
|
||||
WatchItemPathType path_type = kWatchItemPolicyDefaultPathType;
|
||||
|
||||
if (VerifyConfigKey(path_dict, kWatchItemConfigKeyPathsIsPrefix, [NSNumber class], err)) {
|
||||
path_type = ([(NSNumber *)path_dict[kWatchItemConfigKeyPathsIsPrefix] boolValue] == NO
|
||||
? WatchItemPathType::kLiteral
|
||||
: WatchItemPathType::kPrefix);
|
||||
} else {
|
||||
return Unit{};
|
||||
}
|
||||
|
||||
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: %@",
|
||||
(err && *err) ? (*err).localizedDescription
|
||||
: @"Unknown error"]);
|
||||
return Unit{};
|
||||
}
|
||||
|
||||
path_list.push_back(
|
||||
{NSStringToUTF8String(((NSString *)path)), kWatchItemPolicyDefaultPathType});
|
||||
} else {
|
||||
PopulateError(
|
||||
err, [NSString stringWithFormat:
|
||||
@"%@ array item with invalid type. Expected 'dict' or 'string' (got: %@)",
|
||||
kWatchItemConfigKeyPaths, NSStringFromClass([path class])]);
|
||||
return Unit{};
|
||||
}
|
||||
}
|
||||
|
||||
if (path_list.size() == 0) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"No paths specified"]);
|
||||
return Unit{};
|
||||
}
|
||||
|
||||
return path_list;
|
||||
}
|
||||
|
||||
/// The `Processes` array can only contain dictionaries. Each dictionary can
|
||||
/// contain the attributes that describe a single process.
|
||||
///
|
||||
/// <array>
|
||||
/// <dict>
|
||||
/// <key>BinaryPath</key>
|
||||
/// <string>AAAA</string>
|
||||
/// <key>TeamID</key>
|
||||
/// <string>BBBB</string>
|
||||
/// <key>PlatformBinary</key>
|
||||
/// <true/>
|
||||
/// </dict>
|
||||
/// <dict>
|
||||
/// <key>CertificateSha256</key>
|
||||
/// <string>CCCC</string>
|
||||
/// <key>CDHash</key>
|
||||
/// <string>DDDD</string>
|
||||
/// <key>SigningID</key>
|
||||
/// <string>EEEE</string>
|
||||
/// </dict>
|
||||
/// </array>
|
||||
std::variant<Unit, ProcessList> VerifyConfigWatchItemProcesses(NSDictionary *watch_item,
|
||||
NSError **err) {
|
||||
__block ProcessList proc_list;
|
||||
|
||||
if (!VerifyConfigKeyArray(
|
||||
watch_item, kWatchItemConfigKeyProcesses, [NSDictionary class], err,
|
||||
^bool(NSDictionary *process, NSError **err) {
|
||||
if (!VerifyConfigKey(process, kWatchItemConfigKeyProcessesBinaryPath, [NSString class],
|
||||
err, false, LenRangeValidator(1, PATH_MAX)) ||
|
||||
!VerifyConfigKey(process, kWatchItemConfigKeyProcessesSigningID, [NSString class],
|
||||
err, false, LenRangeValidator(1, kMaxSigningIDLength)) ||
|
||||
!VerifyConfigKey(process, kWatchItemConfigKeyProcessesTeamID, [NSString class], err,
|
||||
false, LenRangeValidator(kMaxTeamIDLength, kMaxTeamIDLength)) ||
|
||||
!VerifyConfigKey(process, kWatchItemConfigKeyProcessesCDHash, [NSString class], err,
|
||||
false, HexValidator(CS_CDHASH_LEN * 2)) ||
|
||||
!VerifyConfigKey(process, kWatchItemConfigKeyProcessesCertificateSha256,
|
||||
[NSString class], err, false,
|
||||
HexValidator(CC_SHA256_DIGEST_LENGTH * 2)) ||
|
||||
!VerifyConfigKey(process, kWatchItemConfigKeyProcessesPlatformBinary,
|
||||
[NSNumber class], err, false, nil)) {
|
||||
PopulateError(err, @"Failed to verify key content");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure at least one attribute set
|
||||
if (!process[kWatchItemConfigKeyProcessesBinaryPath] &&
|
||||
!process[kWatchItemConfigKeyProcessesSigningID] &&
|
||||
!process[kWatchItemConfigKeyProcessesTeamID] &&
|
||||
!process[kWatchItemConfigKeyProcessesCDHash] &&
|
||||
!process[kWatchItemConfigKeyProcessesCertificateSha256] &&
|
||||
!process[kWatchItemConfigKeyProcessesPlatformBinary]) {
|
||||
PopulateError(err, @"No valid attributes set in process dictionary");
|
||||
return false;
|
||||
}
|
||||
|
||||
proc_list.push_back(WatchItemPolicy::Process(
|
||||
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesBinaryPath] ?: @""),
|
||||
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesSigningID] ?: @""),
|
||||
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesTeamID] ?: @""),
|
||||
HexStringToBytes(process[kWatchItemConfigKeyProcessesCDHash]),
|
||||
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesCertificateSha256] ?: @""),
|
||||
process[kWatchItemConfigKeyProcessesPlatformBinary]
|
||||
? std::make_optional(
|
||||
(bool)[process[kWatchItemConfigKeyProcessesPlatformBinary] boolValue])
|
||||
: std::nullopt));
|
||||
|
||||
return true;
|
||||
})) {
|
||||
return Unit{};
|
||||
}
|
||||
|
||||
return proc_list;
|
||||
}
|
||||
|
||||
/// Ensure that a given watch item conforms to expected structure
|
||||
///
|
||||
/// Example:
|
||||
/// <dict>
|
||||
/// <key>Paths</key>
|
||||
/// <array>
|
||||
/// ... See VerifyConfigWatchItemPaths for more details ...
|
||||
/// </array>
|
||||
/// <key>Options</key>
|
||||
/// <dict>
|
||||
/// <key>AllowReadAccess</key>
|
||||
/// <false/>
|
||||
/// <key>AuditOnly</key>
|
||||
/// <false/>
|
||||
/// <key>InvertProcessExceptions</key>
|
||||
/// <false/>
|
||||
/// </dict>
|
||||
/// <key>Processes</key>
|
||||
/// <array>
|
||||
/// ... See VerifyConfigWatchItemProcesses for more details ...
|
||||
/// </array>
|
||||
/// </dict>
|
||||
bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
|
||||
std::vector<std::shared_ptr<WatchItemPolicy>> &policies,
|
||||
NSError **err) {
|
||||
if (!VerifyConfigKey(watch_item, kWatchItemConfigKeyPaths, [NSArray class], err, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::variant<Unit, PathList> path_list =
|
||||
VerifyConfigWatchItemPaths(watch_item[kWatchItemConfigKeyPaths], err);
|
||||
|
||||
if (std::holds_alternative<Unit>(path_list)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VerifyConfigKey(watch_item, kWatchItemConfigKeyOptions, [NSDictionary class], err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NSDictionary *options = watch_item[kWatchItemConfigKeyOptions];
|
||||
if (options) {
|
||||
if (!VerifyConfigKey(options, kWatchItemConfigKeyOptionsAllowReadAccess, [NSNumber class],
|
||||
err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VerifyConfigKey(options, kWatchItemConfigKeyOptionsAuditOnly, [NSNumber class], err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VerifyConfigKey(options, kWatchItemConfigKeyOptionsInvertProcessExceptions,
|
||||
[NSNumber class], err)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool allow_read_access = options[kWatchItemConfigKeyOptionsAllowReadAccess]
|
||||
? [options[kWatchItemConfigKeyOptionsAllowReadAccess] boolValue]
|
||||
: kWatchItemPolicyDefaultAllowReadAccess;
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const PathAndTypePair &path_type_pair : std::get<PathList>(path_list)) {
|
||||
policies.push_back(std::make_shared<WatchItemPolicy>(
|
||||
NSStringToUTF8StringView(name), path_type_pair.first, path_type_pair.second,
|
||||
allow_read_access, audit_only, invert_process_exceptions, 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;
|
||||
}
|
||||
|
||||
bool ParseConfig(NSDictionary *config, std::vector<std::shared_ptr<WatchItemPolicy>> &policies,
|
||||
NSError **err) {
|
||||
if (![config[kWatchItemConfigKeyVersion] isKindOfClass:[NSString class]]) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Missing top level string key '%@'",
|
||||
kWatchItemConfigKeyVersion]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ([(NSString *)config[kWatchItemConfigKeyVersion] length] == 0) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Top level key '%@' has empty value",
|
||||
kWatchItemConfigKeyVersion]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config[kWatchItemConfigKeyWatchItems] &&
|
||||
![config[kWatchItemConfigKeyWatchItems] isKindOfClass:[NSDictionary class]]) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"Top level key '%@' must be a dictionary",
|
||||
kWatchItemConfigKeyWatchItems]);
|
||||
return false;
|
||||
}
|
||||
|
||||
NSDictionary *watch_items = config[kWatchItemConfigKeyWatchItems];
|
||||
|
||||
for (id key in watch_items) {
|
||||
if (![key isKindOfClass:[NSString class]]) {
|
||||
PopulateError(err,
|
||||
[NSString stringWithFormat:@"Invalid %@ key %@: Expected type '%@' (got: %@)",
|
||||
kWatchItemConfigKeyWatchItems, key,
|
||||
NSStringFromClass([NSString class]),
|
||||
NSStringFromClass([key class])]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsWatchItemNameValid((NSString *)key, err)) {
|
||||
PopulateError(
|
||||
err, [NSString
|
||||
stringWithFormat:@"Invalid %@ key '%@': %@", kWatchItemConfigKeyWatchItems, key,
|
||||
(err && *err) ? (*err).localizedDescription : @"Unknown failure"]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (![watch_items[key] isKindOfClass:[NSDictionary class]]) {
|
||||
PopulateError(
|
||||
err,
|
||||
[NSString stringWithFormat:@"Value type for watch item '%@' must be a dictionary (got %@)",
|
||||
key, NSStringFromClass([watch_items[key] class])]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseConfigSingleWatchItem(key, watch_items[key], policies, err)) {
|
||||
PopulateError(err, [NSString stringWithFormat:@"In watch item '%@': %@", key,
|
||||
(err && *err) ? (*err).localizedDescription
|
||||
: @"Unknown failure"]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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),
|
||||
watch_items_(std::make_unique<WatchItemsTree>()) {}
|
||||
|
||||
WatchItems::~WatchItems() {
|
||||
if (!periodic_task_started_ && timer_source_ != NULL) {
|
||||
// The timer_source_ must be resumed to ensure it has a proper retain count before being
|
||||
// destroyed. Additionally, it should first be cancelled to ensure the timer isn't ever
|
||||
// fired (see man page for `dispatch_source_cancel(3)`).
|
||||
dispatch_source_cancel(timer_source_);
|
||||
dispatch_resume(timer_source_);
|
||||
}
|
||||
}
|
||||
|
||||
bool WatchItems::BuildPolicyTree(const std::vector<std::shared_ptr<WatchItemPolicy>> &watch_items,
|
||||
PrefixTree<std::shared_ptr<WatchItemPolicy>> &tree,
|
||||
std::set<std::pair<std::string, WatchItemPathType>> &paths) {
|
||||
glob_t *g = (glob_t *)alloca(sizeof(glob_t));
|
||||
for (const std::shared_ptr<WatchItemPolicy> &item : watch_items) {
|
||||
int err = glob(item->path.c_str(), 0, nullptr, g);
|
||||
if (err != 0 && err != GLOB_NOMATCH) {
|
||||
LOGE(@"Failed to generate path names for watch item: %s", item->name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = g->gl_offs; i < g->gl_pathc; i++) {
|
||||
if (item->path_type == WatchItemPathType::kPrefix) {
|
||||
tree.InsertPrefix(g->gl_pathv[i], item);
|
||||
} else {
|
||||
tree.InsertLiteral(g->gl_pathv[i], item);
|
||||
}
|
||||
|
||||
paths.insert({g->gl_pathv[i], item->path_type});
|
||||
}
|
||||
globfree(g);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WatchItems::RegisterClient(id<SNTEndpointSecurityDynamicEventHandler> client) {
|
||||
absl::MutexLock lock(&lock_);
|
||||
registerd_clients_.insert(client);
|
||||
}
|
||||
|
||||
void WatchItems::UpdateCurrentState(
|
||||
std::unique_ptr<PrefixTree<std::shared_ptr<WatchItemPolicy>>> new_tree,
|
||||
std::set<std::pair<std::string, WatchItemPathType>> &&new_monitored_paths,
|
||||
NSDictionary *new_config) {
|
||||
absl::MutexLock lock(&lock_);
|
||||
|
||||
// The following conditions require updating the current config:
|
||||
// 1. The current config doesn't exist but the new one does
|
||||
// 2. The current config exists but the new one doesn't
|
||||
// 3. The set of monitored paths changed
|
||||
// 4. The configuration changed
|
||||
if ((current_config_ != nil && new_config == nil) ||
|
||||
(current_config_ == nil && new_config != nil) ||
|
||||
(currently_monitored_paths_ != new_monitored_paths) ||
|
||||
(new_config && ![current_config_ isEqualToDictionary:new_config])) {
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths_to_watch;
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths_to_stop_watching;
|
||||
|
||||
// New paths to watch are those that are in the new set, but not current
|
||||
std::set_difference(new_monitored_paths.begin(), new_monitored_paths.end(),
|
||||
currently_monitored_paths_.begin(), currently_monitored_paths_.end(),
|
||||
std::back_inserter(paths_to_watch));
|
||||
|
||||
// Paths to stop watching are in the current set, but not new
|
||||
std::set_difference(currently_monitored_paths_.begin(), currently_monitored_paths_.end(),
|
||||
new_monitored_paths.begin(), new_monitored_paths.end(),
|
||||
std::back_inserter(paths_to_stop_watching));
|
||||
|
||||
std::swap(watch_items_, new_tree);
|
||||
std::swap(currently_monitored_paths_, new_monitored_paths);
|
||||
current_config_ = new_config;
|
||||
if (new_config) {
|
||||
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
|
||||
// trigger AUTH ES events that would attempt to re-enter this object and
|
||||
// potentially deadlock.
|
||||
dispatch_async(q_, ^{
|
||||
[client watchItemsCount:currently_monitored_paths_.size()
|
||||
newPaths:paths_to_watch
|
||||
removedPaths:paths_to_stop_watching];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
LOGD(@"No changes to set of watched paths.");
|
||||
}
|
||||
}
|
||||
|
||||
void WatchItems::ReloadConfig(NSDictionary *new_config) {
|
||||
std::vector<std::shared_ptr<WatchItemPolicy>> new_policies;
|
||||
auto new_tree = std::make_unique<PrefixTree<std::shared_ptr<WatchItemPolicy>>>();
|
||||
std::set<std::pair<std::string, WatchItemPathType>> new_monitored_paths;
|
||||
|
||||
if (new_config) {
|
||||
NSError *err;
|
||||
if (!ParseConfig(new_config, new_policies, &err)) {
|
||||
LOGE(@"Failed to parse watch item config: %@",
|
||||
err ? err.localizedDescription : @"Unknown failure");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BuildPolicyTree(new_policies, *new_tree, new_monitored_paths)) {
|
||||
LOGE(@"Failed to build new filesystem monitoring policy");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCurrentState(std::move(new_tree), std::move(new_monitored_paths), new_config);
|
||||
}
|
||||
|
||||
NSDictionary *WatchItems::ReadConfig() {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return ReadConfigLocked();
|
||||
}
|
||||
|
||||
NSDictionary *WatchItems::ReadConfigLocked() {
|
||||
if (config_path_) {
|
||||
return [NSDictionary dictionaryWithContentsOfFile:config_path_];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
void WatchItems::BeginPeriodicTask() {
|
||||
if (periodic_task_started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::weak_ptr<WatchItems> weak_watcher = weak_from_this();
|
||||
dispatch_source_set_event_handler(timer_source_, ^{
|
||||
std::shared_ptr<WatchItems> shared_watcher = weak_watcher.lock();
|
||||
if (!shared_watcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
shared_watcher->ReloadConfig(embedded_config_ ?: shared_watcher->ReadConfig());
|
||||
|
||||
if (shared_watcher->periodic_task_complete_f_) {
|
||||
shared_watcher->periodic_task_complete_f_();
|
||||
}
|
||||
});
|
||||
|
||||
dispatch_resume(timer_source_);
|
||||
periodic_task_started_ = true;
|
||||
}
|
||||
|
||||
WatchItems::VersionAndPolicies WatchItems::FindPolciesForPaths(
|
||||
const std::vector<std::string_view> &paths) {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
std::vector<std::optional<std::shared_ptr<WatchItemPolicy>>> policies;
|
||||
|
||||
for (const auto &path : paths) {
|
||||
policies.push_back(watch_items_->LookupLongestMatchingPrefix(path.data()));
|
||||
}
|
||||
|
||||
return {policy_version_, policies};
|
||||
}
|
||||
|
||||
void WatchItems::SetConfigPath(NSString *config_path) {
|
||||
// Acquire the lock to set the config path and read the config, but drop
|
||||
// the lock before reloading the config
|
||||
NSDictionary *config;
|
||||
{
|
||||
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_);
|
||||
|
||||
if (!current_config_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
WatchItemsState state = {
|
||||
.rule_count = [current_config_[kWatchItemConfigKeyWatchItems] count],
|
||||
.policy_version = [NSString stringWithUTF8String:policy_version_.c_str()],
|
||||
.config_path = [config_path_ copy],
|
||||
.last_config_load_epoch = last_update_time_,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
885
Source/santad/DataLayer/WatchItemsTest.mm
Normal file
885
Source/santad/DataLayer/WatchItemsTest.mm
Normal file
@@ -0,0 +1,885 @@
|
||||
/// 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 <CommonCrypto/CommonDigest.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <Kernel/kern/cs_blobs.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <sys/syslimits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/common/TestUtils.h"
|
||||
#import "Source/common/Unit.h"
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/DataLayer/WatchItems.h"
|
||||
|
||||
using santa::common::Unit;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultAllowReadAccess;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultAuditOnly;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultInvertProcessExceptions;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultPathType;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
using santa::santad::data_layer::WatchItems;
|
||||
using santa::santad::data_layer::WatchItemsState;
|
||||
|
||||
namespace santatest {
|
||||
using PathAndTypePair = std::pair<std::string, WatchItemPathType>;
|
||||
using PathList = std::vector<PathAndTypePair>;
|
||||
using ProcessList = std::vector<WatchItemPolicy::Process>;
|
||||
} // namespace santatest
|
||||
|
||||
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);
|
||||
extern std::variant<Unit, santatest::PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths,
|
||||
NSError **err);
|
||||
extern std::variant<Unit, santatest::ProcessList> VerifyConfigWatchItemProcesses(
|
||||
NSDictionary *watch_item, NSError **err);
|
||||
class WatchItemsPeer : public WatchItems {
|
||||
public:
|
||||
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;
|
||||
using santa::santad::data_layer::VerifyConfigWatchItemProcesses;
|
||||
using santa::santad::data_layer::WatchItemsPeer;
|
||||
|
||||
static constexpr std::string_view kBadPolicyName("__BAD_NAME__");
|
||||
static constexpr std::string_view kBadPolicyPath("__BAD_PATH__");
|
||||
static constexpr std::string_view kVersion("v0.1");
|
||||
|
||||
static std::shared_ptr<WatchItemPolicy> MakeBadPolicy() {
|
||||
return std::make_shared<WatchItemPolicy>(kBadPolicyName, kBadPolicyPath);
|
||||
}
|
||||
|
||||
static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
return [@{@"Version" : @(kVersion.data()), @"WatchItems" : [config mutableCopy]} mutableCopy];
|
||||
}
|
||||
|
||||
static NSString *RepeatedString(NSString *str, NSUInteger len) {
|
||||
return [@"" stringByPaddingToLength:len withString:str startingAtIndex:0];
|
||||
}
|
||||
|
||||
@interface WatchItemsTest : XCTestCase
|
||||
@property NSFileManager *fileMgr;
|
||||
@property NSString *testDir;
|
||||
@property NSMutableArray *dirStack;
|
||||
@property dispatch_queue_t q;
|
||||
@end
|
||||
|
||||
@implementation WatchItemsTest
|
||||
|
||||
- (void)setUp {
|
||||
self.dirStack = [[NSMutableArray alloc] init];
|
||||
self.fileMgr = [NSFileManager defaultManager];
|
||||
self.testDir =
|
||||
[NSString stringWithFormat:@"%@santa-watchitems-%d", NSTemporaryDirectory(), getpid()];
|
||||
|
||||
XCTAssertTrue([self.fileMgr createDirectoryAtPath:self.testDir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil]);
|
||||
|
||||
self.q = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
|
||||
XCTAssertNotNil(self.q);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
XCTAssertTrue([self.fileMgr removeItemAtPath:self.testDir error:nil]);
|
||||
}
|
||||
|
||||
- (void)pushd:(NSString *)path withRoot:(NSString *)root {
|
||||
NSString *dir = [NSString pathWithComponents:@[ root, path ]];
|
||||
NSString *origCwd = [self.fileMgr currentDirectoryPath];
|
||||
XCTAssertNotNil(origCwd);
|
||||
|
||||
XCTAssertTrue([self.fileMgr changeCurrentDirectoryPath:dir]);
|
||||
[self.dirStack addObject:origCwd];
|
||||
}
|
||||
|
||||
- (void)pushd:(NSString *)dir {
|
||||
[self pushd:dir withRoot:self.testDir];
|
||||
}
|
||||
|
||||
- (void)popd {
|
||||
NSString *dir = [self.dirStack lastObject];
|
||||
XCTAssertTrue([self.fileMgr changeCurrentDirectoryPath:dir]);
|
||||
[self.dirStack removeLastObject];
|
||||
}
|
||||
|
||||
- (void)createTestDirStructure:(NSArray *)fs rootedAt:(NSString *)root {
|
||||
NSString *origCwd = [self.fileMgr currentDirectoryPath];
|
||||
XCTAssertNotNil(origCwd);
|
||||
XCTAssertTrue([self.fileMgr changeCurrentDirectoryPath:root]);
|
||||
|
||||
for (id item in fs) {
|
||||
if ([item isKindOfClass:[NSString class]]) {
|
||||
XCTAssertTrue([self.fileMgr createFileAtPath:item contents:nil attributes:nil]);
|
||||
} else if ([item isKindOfClass:[NSDictionary class]]) {
|
||||
for (id dir in item) {
|
||||
XCTAssertTrue([item[dir] isKindOfClass:[NSArray class]]);
|
||||
XCTAssertTrue([self.fileMgr createDirectoryAtPath:dir
|
||||
withIntermediateDirectories:NO
|
||||
attributes:nil
|
||||
error:nil]);
|
||||
|
||||
[self createTestDirStructure:item[dir] rootedAt:dir];
|
||||
}
|
||||
} else {
|
||||
XCTFail("Unexpected dir structure item: %@: %@", item, [item class]);
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertTrue([self.fileMgr changeCurrentDirectoryPath:origCwd]);
|
||||
}
|
||||
|
||||
- (void)createTestDirStructure:(NSArray *)fs {
|
||||
[self createTestDirStructure:fs rootedAt:self.testDir];
|
||||
}
|
||||
|
||||
- (void)testReloadScenarios {
|
||||
[self createTestDirStructure:@[
|
||||
@{
|
||||
@"a" : @[ @"f1", @"f2" ],
|
||||
},
|
||||
@{
|
||||
@"b" : @[ @"f1" ],
|
||||
},
|
||||
]];
|
||||
|
||||
NSDictionary *allFilesPolicy = @{kWatchItemConfigKeyPaths : @[ @"*" ]};
|
||||
NSDictionary *configAllFilesOriginal =
|
||||
WrapWatchItemsConfig(@{@"all_files_orig" : allFilesPolicy});
|
||||
NSDictionary *configAllFilesRename =
|
||||
WrapWatchItemsConfig(@{@"all_files_rename" : allFilesPolicy});
|
||||
|
||||
WatchItems::VersionAndPolicies policies;
|
||||
|
||||
std::vector<std::string_view> f1Path = {"f1"};
|
||||
std::vector<std::string_view> f2Path = {"f2"};
|
||||
|
||||
// Changes in config dictionary will update policy info even if the
|
||||
// filesystem didn't change.
|
||||
{
|
||||
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
|
||||
[self pushd:@"a"];
|
||||
watchItems.ReloadConfig(configAllFilesOriginal);
|
||||
|
||||
policies = watchItems.FindPolciesForPaths(f1Path);
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
"all_files_orig");
|
||||
|
||||
watchItems.ReloadConfig(configAllFilesRename);
|
||||
policies = watchItems.FindPolciesForPaths(f1Path);
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
"all_files_rename");
|
||||
|
||||
policies = watchItems.FindPolciesForPaths(f1Path);
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
"all_files_rename");
|
||||
[self popd];
|
||||
}
|
||||
|
||||
// Changes to fileystem structure are reflected when a config is reloaded
|
||||
{
|
||||
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
|
||||
[self pushd:@"a"];
|
||||
watchItems.ReloadConfig(configAllFilesOriginal);
|
||||
[self popd];
|
||||
|
||||
policies = watchItems.FindPolciesForPaths(f2Path);
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
"all_files_orig");
|
||||
|
||||
[self pushd:@"b"];
|
||||
watchItems.ReloadConfig(configAllFilesOriginal);
|
||||
[self popd];
|
||||
|
||||
policies = watchItems.FindPolciesForPaths(f2Path);
|
||||
XCTAssertFalse(policies.second[0].has_value());
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testPeriodicTask {
|
||||
// Ensure watch item policy memory is properly handled
|
||||
[self createTestDirStructure:@[ @"f1", @"f2", @"weird1" ]];
|
||||
|
||||
NSDictionary *fFiles = @{
|
||||
kWatchItemConfigKeyPaths : @[ @{
|
||||
kWatchItemConfigKeyPathsPath : @"f?",
|
||||
kWatchItemConfigKeyPathsIsPrefix : @(NO),
|
||||
} ]
|
||||
};
|
||||
NSDictionary *weirdFiles = @{
|
||||
kWatchItemConfigKeyPaths : @[ @{
|
||||
kWatchItemConfigKeyPathsPath : @"weird?",
|
||||
kWatchItemConfigKeyPathsIsPrefix : @(NO),
|
||||
} ]
|
||||
};
|
||||
|
||||
NSString *configFile = @"config.plist";
|
||||
NSDictionary *firstConfig = WrapWatchItemsConfig(@{@"f_files" : fFiles});
|
||||
NSDictionary *secondConfig =
|
||||
WrapWatchItemsConfig(@{@"f_files" : fFiles, @"weird_files" : weirdFiles});
|
||||
|
||||
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.q);
|
||||
|
||||
const uint64 periodicFlushMS = 1000;
|
||||
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0),
|
||||
NSEC_PER_MSEC * periodicFlushMS, 0);
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
auto watchItems = std::make_shared<WatchItemsPeer>(configFile, self.q, timer, ^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
});
|
||||
|
||||
// Move into the base test directory and write the config to disk
|
||||
[self pushd:@""];
|
||||
XCTAssertTrue([firstConfig writeToFile:configFile atomically:YES]);
|
||||
|
||||
std::vector<std::string_view> f1Path = {"f1"};
|
||||
std::vector<std::string_view> weird1Path = {"weird1"};
|
||||
|
||||
// Ensure no policy has been loaded yet
|
||||
XCTAssertFalse(watchItems->FindPolciesForPaths(f1Path).second[0].has_value());
|
||||
XCTAssertFalse(watchItems->FindPolciesForPaths(weird1Path).second[0].has_value());
|
||||
|
||||
// Begin the periodic task
|
||||
watchItems->BeginPeriodicTask();
|
||||
|
||||
// The first run of the task starts immediately
|
||||
// Wait for the first iteration and check for the expected policy
|
||||
XCTAssertSemaTrue(sema, 5, "Periodic task did not complete within expected window");
|
||||
XCTAssertTrue(watchItems->FindPolciesForPaths(f1Path).second[0].has_value());
|
||||
XCTAssertFalse(watchItems->FindPolciesForPaths(weird1Path).second[0].has_value());
|
||||
|
||||
// Write the config update
|
||||
XCTAssertTrue([secondConfig writeToFile:configFile atomically:YES]);
|
||||
|
||||
// Wait for the new config to be loaded and check for the new expected policies
|
||||
XCTAssertSemaTrue(sema, 5, "Periodic task did not complete within expected window");
|
||||
XCTAssertTrue(watchItems->FindPolciesForPaths(f1Path).second[0].has_value());
|
||||
XCTAssertTrue(watchItems->FindPolciesForPaths(weird1Path).second[0].has_value());
|
||||
|
||||
[self popd];
|
||||
}
|
||||
|
||||
- (void)testPolicyLookup {
|
||||
// Test multiple, more comprehensive policies before/after config reload
|
||||
[self createTestDirStructure:@[
|
||||
@{
|
||||
@"foo" : @[ @"bar.txt", @"bar.txt.tmp" ],
|
||||
@"baz" : @[ @{@"qaz" : @[]} ],
|
||||
},
|
||||
@"f1",
|
||||
]];
|
||||
|
||||
NSMutableDictionary *config = WrapWatchItemsConfig(@{
|
||||
@"foo_subdir" : @{
|
||||
kWatchItemConfigKeyPaths : @[ @{
|
||||
kWatchItemConfigKeyPathsPath : @"./foo",
|
||||
kWatchItemConfigKeyPathsIsPrefix : @(YES),
|
||||
} ]
|
||||
}
|
||||
});
|
||||
|
||||
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
|
||||
WatchItems::VersionAndPolicies policies;
|
||||
|
||||
// Resultant vector is same size as input vector
|
||||
// Initially nothing should be in the map
|
||||
std::vector<std::string_view> paths = {};
|
||||
XCTAssertEqual(watchItems.FindPolciesForPaths(paths).second.size(), 0);
|
||||
paths.push_back("./foo");
|
||||
XCTAssertEqual(watchItems.FindPolciesForPaths(paths).second.size(), 1);
|
||||
XCTAssertFalse(watchItems.FindPolciesForPaths(paths).second[0].has_value());
|
||||
paths.push_back("./baz");
|
||||
XCTAssertEqual(watchItems.FindPolciesForPaths(paths).second.size(), 2);
|
||||
|
||||
// Load the initial config
|
||||
[self pushd:@""];
|
||||
watchItems.ReloadConfig(config);
|
||||
[self popd];
|
||||
|
||||
{
|
||||
// Test expected values with the inital policy
|
||||
const std::map<std::vector<std::string_view>, std::string_view> pathToPolicyName = {
|
||||
{{"./foo"}, "foo_subdir"},
|
||||
{{"./foo/bar.txt.tmp"}, "foo_subdir"},
|
||||
{{"./foo/bar.txt"}, "foo_subdir"},
|
||||
{{"./does/not/exist"}, kBadPolicyName},
|
||||
};
|
||||
|
||||
for (const auto &kv : pathToPolicyName) {
|
||||
policies = watchItems.FindPolciesForPaths(kv.first);
|
||||
XCTAssertCStringEqual(policies.first.data(), kVersion.data());
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
kv.second.data());
|
||||
}
|
||||
|
||||
// Test multiple lookup
|
||||
policies = watchItems.FindPolciesForPaths({"./foo", "./does/not/exist"});
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(), "foo_subdir");
|
||||
XCTAssertFalse(policies.second[1].has_value());
|
||||
}
|
||||
|
||||
// Add a new policy and reload the config
|
||||
NSDictionary *barTxtFilePolicy = @{
|
||||
kWatchItemConfigKeyPaths : @[ @{
|
||||
kWatchItemConfigKeyPathsPath : @"./foo/bar.txt",
|
||||
kWatchItemConfigKeyPathsIsPrefix : @(NO),
|
||||
} ]
|
||||
};
|
||||
[config[@"WatchItems"] setObject:barTxtFilePolicy forKey:@"bar_txt"];
|
||||
|
||||
// Load the updated config
|
||||
[self pushd:@""];
|
||||
watchItems.ReloadConfig(config);
|
||||
[self popd];
|
||||
|
||||
{
|
||||
// Test expected values with the updated policy
|
||||
const std::map<std::vector<std::string_view>, std::string_view> pathToPolicyName = {
|
||||
{{"./foo"}, "foo_subdir"},
|
||||
{{"./foo/bar.txt.tmp"}, "foo_subdir"},
|
||||
{{"./foo/bar.txt"}, "bar_txt"},
|
||||
{{"./does/not/exist"}, kBadPolicyName},
|
||||
};
|
||||
|
||||
for (const auto &kv : pathToPolicyName) {
|
||||
policies = watchItems.FindPolciesForPaths(kv.first);
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
kv.second.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Add a catch-all policy that should only affect the previously non-matching path
|
||||
NSDictionary *catchAllFilePolicy = @{
|
||||
kWatchItemConfigKeyPaths : @[ @{
|
||||
kWatchItemConfigKeyPathsPath : @".",
|
||||
kWatchItemConfigKeyPathsIsPrefix : @(YES),
|
||||
} ]
|
||||
};
|
||||
[config[@"WatchItems"] setObject:catchAllFilePolicy forKey:@"dot_everything"];
|
||||
|
||||
// Load the updated config
|
||||
[self pushd:@""];
|
||||
watchItems.ReloadConfig(config);
|
||||
[self popd];
|
||||
|
||||
{
|
||||
// Test expected values with the catch-all policy
|
||||
const std::map<std::vector<std::string_view>, std::string_view> pathToPolicyName = {
|
||||
{{"./foo"}, "foo_subdir"},
|
||||
{{"./foo/bar.txt.tmp"}, "foo_subdir"},
|
||||
{{"./foo/bar.txt"}, "bar_txt"},
|
||||
{{"./does/not/exist"}, "dot_everything"},
|
||||
};
|
||||
|
||||
for (const auto &kv : pathToPolicyName) {
|
||||
policies = watchItems.FindPolciesForPaths(kv.first);
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
kv.second.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove the foo_subdir rule, previous matches should fallback to the catch-all
|
||||
[config[@"WatchItems"] removeObjectForKey:@"foo_subdir"];
|
||||
[self pushd:@""];
|
||||
watchItems.ReloadConfig(config);
|
||||
[self popd];
|
||||
|
||||
{
|
||||
// Test expected values with the foo_subdir policy removed
|
||||
const std::map<std::vector<std::string_view>, std::string_view> pathToPolicyName = {
|
||||
{{"./foo"}, "dot_everything"},
|
||||
{{"./foo/bar.txt.tmp"}, "dot_everything"},
|
||||
{{"./foo/bar.txt"}, "bar_txt"},
|
||||
{{"./does/not/exist"}, "dot_everything"},
|
||||
};
|
||||
|
||||
for (const auto &kv : pathToPolicyName) {
|
||||
policies = watchItems.FindPolciesForPaths(kv.first);
|
||||
XCTAssertCStringEqual(policies.second[0].value_or(MakeBadPolicy())->name.c_str(),
|
||||
kv.second.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testVerifyConfigWatchItemPaths {
|
||||
std::variant<Unit, santatest::PathList> path_list;
|
||||
NSError *err;
|
||||
|
||||
// Test no paths specified
|
||||
path_list = VerifyConfigWatchItemPaths(@[], &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(path_list));
|
||||
|
||||
// Test invalid types in paths array
|
||||
path_list = VerifyConfigWatchItemPaths(@[ @(0) ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(path_list));
|
||||
|
||||
// Test path array with long string
|
||||
path_list = VerifyConfigWatchItemPaths(@[ RepeatedString(@"A", PATH_MAX + 1) ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(path_list));
|
||||
|
||||
// Test path array dictionary with missing required key
|
||||
path_list = VerifyConfigWatchItemPaths(@[ @{@"FakePath" : @"A"} ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(path_list));
|
||||
|
||||
// Test path array dictionary with long string
|
||||
path_list = VerifyConfigWatchItemPaths(
|
||||
@[ @{kWatchItemConfigKeyPathsPath : RepeatedString(@"A", PATH_MAX + 1)} ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(path_list));
|
||||
|
||||
// Test path array dictionary with default path type
|
||||
path_list = VerifyConfigWatchItemPaths(@[ @{kWatchItemConfigKeyPathsPath : @"A"} ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::PathList>(path_list));
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list).size(), 1);
|
||||
XCTAssertCStringEqual(std::get<santatest::PathList>(path_list)[0].first.c_str(), "A");
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list)[0].second,
|
||||
kWatchItemPolicyDefaultPathType);
|
||||
|
||||
// Test path array dictionary with custom path type
|
||||
path_list = VerifyConfigWatchItemPaths(
|
||||
@[ @{kWatchItemConfigKeyPathsPath : @"A", kWatchItemConfigKeyPathsIsPrefix : @(YES)} ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::PathList>(path_list));
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list).size(), 1);
|
||||
XCTAssertCStringEqual(std::get<santatest::PathList>(path_list)[0].first.c_str(), "A");
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list)[0].second, WatchItemPathType::kPrefix);
|
||||
}
|
||||
|
||||
- (void)testVerifyConfigWatchItemProcesses {
|
||||
std::variant<Unit, santatest::ProcessList> proc_list;
|
||||
NSError *err;
|
||||
|
||||
// Non-existent process list parses successfully, but has no items
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 0);
|
||||
|
||||
// Process list fails to parse if contains non-array type
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @""}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @(0)}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @{}}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @[]}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
|
||||
// Test a process dictionary with no valid attributes set
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @[ @{} ]}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test BinaryPath length limits
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses :
|
||||
@[ @{kWatchItemConfigKeyProcessesBinaryPath : RepeatedString(@"A", PATH_MAX + 1)} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test valid BinaryPath
|
||||
proc_list = VerifyConfigWatchItemProcesses(
|
||||
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesBinaryPath : @"mypath"} ]},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
WatchItemPolicy::Process("mypath", "", "", {}, "", std::nullopt));
|
||||
|
||||
// Test SigningID length limits
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses :
|
||||
@[ @{kWatchItemConfigKeyProcessesSigningID : RepeatedString(@"A", 513)} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test valid SigningID
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses :
|
||||
@[ @{kWatchItemConfigKeyProcessesSigningID : @"com.google.test"} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "com.google.test", "", {}, "", std::nullopt));
|
||||
|
||||
// Test TeamID length limits
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses :
|
||||
@[ @{kWatchItemConfigKeyProcessesTeamID : @"LongerThanExpectedTeamID"} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test valid TeamID
|
||||
proc_list = VerifyConfigWatchItemProcesses(
|
||||
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesTeamID : @"myvalidtid"} ]},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "", "myvalidtid", {}, "", std::nullopt));
|
||||
|
||||
// Test CDHash length limits
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses :
|
||||
@[ @{kWatchItemConfigKeyProcessesCDHash : RepeatedString(@"A", CS_CDHASH_LEN * 2 + 1)} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test CDHash hex-only
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses :
|
||||
@[ @{kWatchItemConfigKeyProcessesCDHash : RepeatedString(@"Z", CS_CDHASH_LEN * 2)} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test valid CDHash
|
||||
NSString *cdhash = RepeatedString(@"A", CS_CDHASH_LEN * 2);
|
||||
std::vector<uint8_t> cdhashBytes(cdhash.length / 2);
|
||||
std::fill(cdhashBytes.begin(), cdhashBytes.end(), 0xAA);
|
||||
proc_list = VerifyConfigWatchItemProcesses(
|
||||
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesCDHash : cdhash} ]}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "", "", cdhashBytes, "", std::nullopt));
|
||||
|
||||
// Test Cert Hash length limits
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses : @[ @{
|
||||
kWatchItemConfigKeyProcessesCertificateSha256 :
|
||||
RepeatedString(@"A", CC_SHA256_DIGEST_LENGTH * 2 + 1)
|
||||
} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test Cert Hash hex-only
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses : @[ @{
|
||||
kWatchItemConfigKeyProcessesCertificateSha256 :
|
||||
RepeatedString(@"Z", CC_SHA256_DIGEST_LENGTH * 2)
|
||||
} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
|
||||
// Test valid Cert Hash
|
||||
NSString *certHash = RepeatedString(@"A", CC_SHA256_DIGEST_LENGTH * 2);
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{
|
||||
kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesCertificateSha256 : certHash} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
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(@{
|
||||
kWatchItemConfigKeyProcesses : @[
|
||||
@{
|
||||
kWatchItemConfigKeyProcessesBinaryPath : @"mypath1",
|
||||
kWatchItemConfigKeyProcessesSigningID : @"com.google.test1",
|
||||
kWatchItemConfigKeyProcessesTeamID : @"validtid_1",
|
||||
kWatchItemConfigKeyProcessesCDHash : cdhash,
|
||||
kWatchItemConfigKeyProcessesCertificateSha256 : certHash,
|
||||
kWatchItemConfigKeyProcessesPlatformBinary : @(YES),
|
||||
},
|
||||
@{
|
||||
kWatchItemConfigKeyProcessesBinaryPath : @"mypath2",
|
||||
kWatchItemConfigKeyProcessesSigningID : @"com.google.test2",
|
||||
kWatchItemConfigKeyProcessesTeamID : @"validtid_2",
|
||||
kWatchItemConfigKeyProcessesCDHash : cdhash,
|
||||
kWatchItemConfigKeyProcessesCertificateSha256 : certHash,
|
||||
kWatchItemConfigKeyProcessesPlatformBinary : @(NO),
|
||||
},
|
||||
]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 2);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
WatchItemPolicy::Process("mypath1", "com.google.test1", "validtid_1", cdhashBytes,
|
||||
[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], 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 {
|
||||
NSError *err;
|
||||
std::vector<std::shared_ptr<WatchItemPolicy>> policies;
|
||||
|
||||
// Ensure top level keys must exist and be correct types
|
||||
XCTAssertFalse(ParseConfig(@{}, policies, &err));
|
||||
XCTAssertFalse(ParseConfig(@{kWatchItemConfigKeyVersion : @(0)}, policies, &err));
|
||||
XCTAssertFalse(ParseConfig(@{kWatchItemConfigKeyVersion : @{}}, policies, &err));
|
||||
XCTAssertFalse(ParseConfig(@{kWatchItemConfigKeyVersion : @[]}, policies, &err));
|
||||
XCTAssertFalse(ParseConfig(@{kWatchItemConfigKeyVersion : @""}, policies, &err));
|
||||
XCTAssertFalse(ParseConfig(
|
||||
@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @""}, policies, &err));
|
||||
XCTAssertFalse(ParseConfig(
|
||||
@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @[]}, policies, &err));
|
||||
XCTAssertFalse(ParseConfig(
|
||||
@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @(0)}, policies, &err));
|
||||
|
||||
// Minimally successful configs without watch items
|
||||
XCTAssertTrue(ParseConfig(@{kWatchItemConfigKeyVersion : @"1"}, policies, &err));
|
||||
XCTAssertTrue(ParseConfig(
|
||||
@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{}}, policies, &err));
|
||||
|
||||
// Ensure constraints on watch items entries match expectations
|
||||
XCTAssertFalse(ParseConfig(
|
||||
@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@(0) : @(0)}}, policies,
|
||||
&err));
|
||||
XCTAssertFalse(
|
||||
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"" : @{}}},
|
||||
policies, &err));
|
||||
XCTAssertFalse(
|
||||
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"a" : @[]}},
|
||||
policies, &err));
|
||||
XCTAssertFalse(
|
||||
ParseConfig(@{kWatchItemConfigKeyVersion : @"1", kWatchItemConfigKeyWatchItems : @{@"a" : @{}}},
|
||||
policies, &err));
|
||||
|
||||
// Minimally successful config with watch item
|
||||
XCTAssertTrue(ParseConfig(@{
|
||||
kWatchItemConfigKeyVersion : @"1",
|
||||
kWatchItemConfigKeyWatchItems : @{@"a" : @{kWatchItemConfigKeyPaths : @[ @"asdf" ]}}
|
||||
},
|
||||
policies, &err));
|
||||
}
|
||||
|
||||
- (void)testParseConfigSingleWatchItem {
|
||||
std::vector<std::shared_ptr<WatchItemPolicy>> policies;
|
||||
NSError *err;
|
||||
|
||||
// There must be valid Paths in a watch item
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(@"", @{}, policies, &err));
|
||||
XCTAssertFalse(
|
||||
ParseConfigSingleWatchItem(@"", @{kWatchItemConfigKeyPaths : @[ @"" ]}, policies, &err));
|
||||
XCTAssertTrue(
|
||||
ParseConfigSingleWatchItem(@"", @{kWatchItemConfigKeyPaths : @[ @"a" ]}, policies, &err));
|
||||
|
||||
// Empty options are fine
|
||||
XCTAssertTrue(ParseConfigSingleWatchItem(
|
||||
@"", @{kWatchItemConfigKeyPaths : @[ @"a" ], kWatchItemConfigKeyOptions : @{}}, policies,
|
||||
&err));
|
||||
|
||||
// If an Options key exist, it must be a dictionary type
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(
|
||||
@"", @{kWatchItemConfigKeyPaths : @[ @"a" ], kWatchItemConfigKeyOptions : @[]}, policies,
|
||||
&err));
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(
|
||||
@"", @{kWatchItemConfigKeyPaths : @[ @"a" ], kWatchItemConfigKeyOptions : @""}, policies,
|
||||
&err));
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(
|
||||
@"", @{kWatchItemConfigKeyPaths : @[ @"a" ], kWatchItemConfigKeyOptions : @(0)}, policies,
|
||||
&err));
|
||||
|
||||
// Options keys must be valid types
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsAllowReadAccess : @""}
|
||||
},
|
||||
policies, &err));
|
||||
XCTAssertTrue(ParseConfigSingleWatchItem(@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsAllowReadAccess : @(0)}
|
||||
},
|
||||
policies, &err));
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsAuditOnly : @""}
|
||||
},
|
||||
policies, &err));
|
||||
XCTAssertTrue(ParseConfigSingleWatchItem(@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsAuditOnly : @(0)}
|
||||
},
|
||||
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(
|
||||
@"", @{kWatchItemConfigKeyPaths : @[ @"a" ], kWatchItemConfigKeyProcesses : @""}, policies,
|
||||
&err));
|
||||
|
||||
// Test the policy vector is populated as expected
|
||||
|
||||
// Test default options with no processes
|
||||
policies.clear();
|
||||
XCTAssertTrue(
|
||||
ParseConfigSingleWatchItem(@"rule", @{kWatchItemConfigKeyPaths : @[ @"a" ]}, policies, &err));
|
||||
XCTAssertEqual(policies.size(), 1);
|
||||
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", "", "", {}, "", std::nullopt),
|
||||
WatchItemPolicy::Process("pb", "", "", {}, "", std::nullopt),
|
||||
};
|
||||
|
||||
XCTAssertTrue(ParseConfigSingleWatchItem(@"rule", @{
|
||||
kWatchItemConfigKeyPaths :
|
||||
@[ @"a", @{kWatchItemConfigKeyPathsPath : @"b", kWatchItemConfigKeyPathsIsPrefix : @(YES)} ],
|
||||
kWatchItemConfigKeyOptions : @{
|
||||
kWatchItemConfigKeyOptionsAllowReadAccess : @(YES),
|
||||
kWatchItemConfigKeyOptionsAuditOnly : @(NO),
|
||||
kWatchItemConfigKeyOptionsInvertProcessExceptions : @(YES),
|
||||
},
|
||||
kWatchItemConfigKeyProcesses : @[
|
||||
@{kWatchItemConfigKeyProcessesBinaryPath : @"pa"},
|
||||
@{kWatchItemConfigKeyProcessesBinaryPath : @"pb"}
|
||||
]
|
||||
},
|
||||
policies, &err));
|
||||
XCTAssertEqual(policies.size(), 2);
|
||||
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 {
|
||||
NSString *configPath = @"my_config_path";
|
||||
NSTimeInterval startTime = [[NSDate date] timeIntervalSince1970];
|
||||
|
||||
NSMutableDictionary *config = WrapWatchItemsConfig(@{
|
||||
@"rule1" : @{kWatchItemConfigKeyPaths : @[ @"abc" ]},
|
||||
@"rule2" : @{kWatchItemConfigKeyPaths : @[ @"xyz" ]}
|
||||
});
|
||||
|
||||
WatchItemsPeer watchItems(configPath, NULL, NULL);
|
||||
|
||||
// If no policy yet exists, nullopt is returned
|
||||
std::optional<WatchItemsState> optionalState = watchItems.State();
|
||||
XCTAssertFalse(optionalState.has_value());
|
||||
|
||||
watchItems.ReloadConfig(config);
|
||||
|
||||
optionalState = watchItems.State();
|
||||
XCTAssertTrue(optionalState.has_value());
|
||||
WatchItemsState state = optionalState.value();
|
||||
|
||||
XCTAssertEqual(state.rule_count, [config[kWatchItemConfigKeyWatchItems] count]);
|
||||
XCTAssertCStringEqual(state.policy_version.UTF8String, kVersion.data());
|
||||
XCTAssertEqual(state.config_path, configPath);
|
||||
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
|
||||
@@ -21,8 +21,10 @@
|
||||
#include <sys/stat.h>
|
||||
#include <memory>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#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"
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
@@ -32,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;
|
||||
@@ -49,22 +64,23 @@ class AuthResultCache {
|
||||
AuthResultCache(const AuthResultCache &other) = delete;
|
||||
AuthResultCache &operator=(const AuthResultCache &other) = delete;
|
||||
|
||||
virtual bool AddToCache(const es_file_t *es_file, santa_action_t decision);
|
||||
virtual bool AddToCache(const es_file_t *es_file, SNTAction decision);
|
||||
virtual void RemoveFromCache(const es_file_t *es_file);
|
||||
virtual santa_action_t CheckCache(const es_file_t *es_file);
|
||||
virtual santa_action_t CheckCache(santa_vnode_id_t vnode_id);
|
||||
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();
|
||||
|
||||
private:
|
||||
virtual SantaCache<santa_vnode_id_t, uint64_t> *CacheForVnodeID(santa_vnode_id_t vnode_id);
|
||||
virtual SantaCache<SantaVnode, uint64_t> *CacheForVnodeID(SantaVnode vnode_id);
|
||||
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *root_cache_;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *nonroot_cache_;
|
||||
SantaCache<SantaVnode, uint64_t> *root_cache_;
|
||||
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_;
|
||||
|
||||
@@ -19,48 +19,70 @@
|
||||
#include <time.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SantaVnodeHash.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
|
||||
}
|
||||
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 santa_vnode_id_t VnodeForFile(const es_file_t *es_file) {
|
||||
return santa_vnode_id_t{
|
||||
.fsid = (uint64_t)es_file->stat.st_dev,
|
||||
.fileid = es_file->stat.st_ino,
|
||||
};
|
||||
}
|
||||
|
||||
static inline uint64_t GetCurrentUptime() {
|
||||
return clock_gettime_nsec_np(CLOCK_MONOTONIC);
|
||||
}
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
static inline uint64_t CacheableAction(santa_action_t action,
|
||||
uint64_t timestamp = GetCurrentUptime()) {
|
||||
static inline uint64_t CacheableAction(SNTAction action, uint64_t timestamp = GetCurrentUptime()) {
|
||||
return ((uint64_t)action << 56) | (timestamp & 0xFFFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
static inline santa_action_t ActionFromCachedValue(uint64_t cachedValue) {
|
||||
return (santa_action_t)(cachedValue >> 56);
|
||||
static inline SNTAction ActionFromCachedValue(uint64_t cachedValue) {
|
||||
return (SNTAction)(cachedValue >> 56);
|
||||
}
|
||||
|
||||
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) {
|
||||
root_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
nonroot_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
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>();
|
||||
|
||||
struct stat sb;
|
||||
if (stat("/", &sb) == 0) {
|
||||
@@ -78,17 +100,17 @@ AuthResultCache::~AuthResultCache() {
|
||||
delete nonroot_cache_;
|
||||
}
|
||||
|
||||
bool AuthResultCache::AddToCache(const es_file_t *es_file, santa_action_t decision) {
|
||||
santa_vnode_id_t vnode_id = VnodeForFile(es_file);
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *cache = CacheForVnodeID(vnode_id);
|
||||
bool AuthResultCache::AddToCache(const es_file_t *es_file, SNTAction decision) {
|
||||
SantaVnode vnode_id = SantaVnode::VnodeForFile(es_file);
|
||||
SantaCache<SantaVnode, uint64_t> *cache = CacheForVnodeID(vnode_id);
|
||||
switch (decision) {
|
||||
case ACTION_REQUEST_BINARY:
|
||||
return cache->set(vnode_id, CacheableAction(ACTION_REQUEST_BINARY, 0), 0);
|
||||
case ACTION_RESPOND_ALLOW: OS_FALLTHROUGH;
|
||||
case ACTION_RESPOND_ALLOW_COMPILER: OS_FALLTHROUGH;
|
||||
case ACTION_RESPOND_DENY:
|
||||
case SNTActionRequestBinary:
|
||||
return cache->set(vnode_id, CacheableAction(SNTActionRequestBinary, 0), 0);
|
||||
case SNTActionRespondAllow: OS_FALLTHROUGH;
|
||||
case SNTActionRespondAllowCompiler: OS_FALLTHROUGH;
|
||||
case SNTActionRespondDeny:
|
||||
return cache->set(vnode_id, CacheableAction(decision),
|
||||
CacheableAction(ACTION_REQUEST_BINARY, 0));
|
||||
CacheableAction(SNTActionRequestBinary, 0));
|
||||
default:
|
||||
// This is a programming error. Bail.
|
||||
LOGE(@"Invalid cache value, exiting.");
|
||||
@@ -97,41 +119,40 @@ bool AuthResultCache::AddToCache(const es_file_t *es_file, santa_action_t decisi
|
||||
}
|
||||
|
||||
void AuthResultCache::RemoveFromCache(const es_file_t *es_file) {
|
||||
santa_vnode_id_t vnode_id = VnodeForFile(es_file);
|
||||
SantaVnode vnode_id = SantaVnode::VnodeForFile(es_file);
|
||||
CacheForVnodeID(vnode_id)->remove(vnode_id);
|
||||
}
|
||||
|
||||
santa_action_t AuthResultCache::CheckCache(const es_file_t *es_file) {
|
||||
return CheckCache(VnodeForFile(es_file));
|
||||
SNTAction AuthResultCache::CheckCache(const es_file_t *es_file) {
|
||||
return CheckCache(SantaVnode::VnodeForFile(es_file));
|
||||
}
|
||||
|
||||
santa_action_t AuthResultCache::CheckCache(santa_vnode_id_t vnode_id) {
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *cache = CacheForVnodeID(vnode_id);
|
||||
SNTAction AuthResultCache::CheckCache(SantaVnode vnode_id) {
|
||||
SantaCache<SantaVnode, uint64_t> *cache = CacheForVnodeID(vnode_id);
|
||||
|
||||
uint64_t cached_val = cache->get(vnode_id);
|
||||
if (cached_val == 0) {
|
||||
return ACTION_UNSET;
|
||||
return SNTActionUnset;
|
||||
}
|
||||
|
||||
santa_action_t result = ActionFromCachedValue(cached_val);
|
||||
SNTAction result = ActionFromCachedValue(cached_val);
|
||||
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
if (result == SNTActionRespondDeny) {
|
||||
uint64_t expiry_time = TimestampFromCachedValue(cached_val) + cache_deny_time_ns_;
|
||||
if (expiry_time < GetCurrentUptime()) {
|
||||
cache->remove(vnode_id);
|
||||
return ACTION_UNSET;
|
||||
return SNTActionUnset;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *AuthResultCache::CacheForVnodeID(
|
||||
santa_vnode_id_t vnode_id) {
|
||||
SantaCache<SantaVnode, uint64_t> *AuthResultCache::CacheForVnodeID(SantaVnode vnode_id) {
|
||||
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();
|
||||
@@ -147,6 +168,8 @@ void AuthResultCache::FlushCache(FlushCacheMode mode) {
|
||||
shared_esapi->ClearCache(Client());
|
||||
});
|
||||
}
|
||||
|
||||
[flush_count_ incrementForFieldValues:@[ FlushCacheReasonToString(reason) ]];
|
||||
}
|
||||
|
||||
NSArray<NSNumber *> *AuthResultCache::CacheCounts() {
|
||||
|
||||
@@ -22,13 +22,20 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Source/common/SNTCommon.h"
|
||||
#include "Source/common/SantaVnode.h"
|
||||
#include "Source/common/TestUtils.h"
|
||||
#include "Source/santad/EventProviders/AuthResultCache.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h"
|
||||
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::FlushCacheMode;
|
||||
using santa::santad::event_providers::FlushCacheReason;
|
||||
|
||||
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() {
|
||||
@@ -47,13 +54,6 @@ static inline es_file_t MakeCacheableFile(uint64_t devno, uint64_t ino) {
|
||||
.path = {}, .path_truncated = false, .stat = {.st_dev = (dev_t)devno, .st_ino = ino}};
|
||||
}
|
||||
|
||||
static inline santa_vnode_id_t VnodeForFile(const es_file_t *es_file) {
|
||||
return santa_vnode_id_t{
|
||||
.fsid = (uint64_t)es_file->stat.st_dev,
|
||||
.fileid = es_file->stat.st_ino,
|
||||
};
|
||||
}
|
||||
|
||||
static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uint64_t root_count,
|
||||
uint64_t nonroot_count) {
|
||||
NSArray<NSNumber *> *counts = cache->CacheCounts();
|
||||
@@ -73,67 +73,67 @@ 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);
|
||||
|
||||
// Add the root file to the cache
|
||||
cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY);
|
||||
cache->AddToCache(&rootFile, SNTActionRequestBinary);
|
||||
|
||||
AssertCacheCounts(cache, 1, 0);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), ACTION_UNSET);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionRequestBinary);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), SNTActionUnset);
|
||||
|
||||
// Now add the non-root file
|
||||
cache->AddToCache(&nonrootFile, ACTION_REQUEST_BINARY);
|
||||
cache->AddToCache(&nonrootFile, SNTActionRequestBinary);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), ACTION_REQUEST_BINARY);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionRequestBinary);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), SNTActionRequestBinary);
|
||||
|
||||
// Update the cached values
|
||||
cache->AddToCache(&rootFile, ACTION_RESPOND_ALLOW);
|
||||
cache->AddToCache(&nonrootFile, ACTION_RESPOND_DENY);
|
||||
cache->AddToCache(&rootFile, SNTActionRespondAllow);
|
||||
cache->AddToCache(&nonrootFile, SNTActionRespondDeny);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
XCTAssertEqual(cache->CheckCache(VnodeForFile(&rootFile)), ACTION_RESPOND_ALLOW);
|
||||
XCTAssertEqual(cache->CheckCache(VnodeForFile(&nonrootFile)), ACTION_RESPOND_DENY);
|
||||
XCTAssertEqual(cache->CheckCache(SantaVnode::VnodeForFile(&rootFile)), SNTActionRespondAllow);
|
||||
XCTAssertEqual(cache->CheckCache(SantaVnode::VnodeForFile(&nonrootFile)), SNTActionRespondDeny);
|
||||
|
||||
// Remove the root file
|
||||
cache->RemoveFromCache(&rootFile);
|
||||
|
||||
AssertCacheCounts(cache, 0, 1);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), ACTION_RESPOND_DENY);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionUnset);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), SNTActionRespondDeny);
|
||||
}
|
||||
|
||||
- (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);
|
||||
|
||||
cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY);
|
||||
cache->AddToCache(&nonrootFile, ACTION_REQUEST_BINARY);
|
||||
cache->AddToCache(&rootFile, SNTActionRequestBinary);
|
||||
cache->AddToCache(&nonrootFile, SNTActionRequestBinary);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
|
||||
// Flush non-root only
|
||||
cache->FlushCache(FlushCacheMode::kNonRootOnly);
|
||||
cache->FlushCache(FlushCacheMode::kNonRootOnly, FlushCacheReason::kClientModeChanged);
|
||||
|
||||
AssertCacheCounts(cache, 1, 0);
|
||||
|
||||
// Add back the non-root file
|
||||
cache->AddToCache(&nonrootFile, ACTION_REQUEST_BINARY);
|
||||
cache->AddToCache(&nonrootFile, SNTActionRequestBinary);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
|
||||
@@ -145,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)),
|
||||
@@ -158,37 +158,37 @@ 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);
|
||||
|
||||
// Cached items must first be in the ACTION_REQUEST_BINARY state
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_RESPOND_ALLOW));
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_RESPOND_ALLOW_COMPILER));
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_RESPOND_DENY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
// Cached items must first be in the SNTActionRequestBinary state
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, SNTActionRespondAllow));
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, SNTActionRespondAllowCompiler));
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, SNTActionRespondDeny));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionUnset);
|
||||
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, SNTActionRequestBinary));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionRequestBinary);
|
||||
|
||||
// Items in the `ACTION_REQUEST_BINARY` state cannot reenter the same state
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
// Items in the `SNTActionRequestBinary` state cannot reenter the same state
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, SNTActionRequestBinary));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionRequestBinary);
|
||||
|
||||
santa_action_t allowed_transitions[] = {
|
||||
ACTION_RESPOND_ALLOW,
|
||||
ACTION_RESPOND_ALLOW_COMPILER,
|
||||
ACTION_RESPOND_DENY,
|
||||
SNTAction allowed_transitions[] = {
|
||||
SNTActionRespondAllow,
|
||||
SNTActionRespondAllowCompiler,
|
||||
SNTActionRespondDeny,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(allowed_transitions) / sizeof(allowed_transitions[0]); i++) {
|
||||
// First make sure the item doesn't exist
|
||||
cache->RemoveFromCache(&rootFile);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionUnset);
|
||||
|
||||
// Now add the item to be in the first allowed state
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, SNTActionRequestBinary));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionRequestBinary);
|
||||
|
||||
// Now assert the allowed transition
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, allowed_transitions[i]));
|
||||
@@ -200,16 +200,16 @@ 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);
|
||||
|
||||
// Add a file to the cache and put into the ACTION_RESPOND_DENY state
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_RESPOND_DENY));
|
||||
// Add a file to the cache and put into the SNTActionRespondDeny state
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, SNTActionRequestBinary));
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, SNTActionRespondDeny));
|
||||
|
||||
// Ensure the file exists
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_RESPOND_DENY);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionRespondDeny);
|
||||
|
||||
// Wait for the item to expire
|
||||
SleepMS(expiryMS);
|
||||
@@ -218,8 +218,25 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
|
||||
AssertCacheCounts(cache, 1, 0);
|
||||
|
||||
// Now check the cache, which will remove the item
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), SNTActionUnset);
|
||||
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
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
@@ -32,12 +34,26 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
|
||||
virtual Client NewClient(void (^message_handler)(es_client_t *, Message));
|
||||
|
||||
virtual bool Subscribe(const Client &client, const std::set<es_event_type_t> &);
|
||||
virtual bool UnsubscribeAll(const Client &client);
|
||||
|
||||
virtual es_message_t *RetainMessage(const es_message_t *msg);
|
||||
virtual void ReleaseMessage(es_message_t *msg);
|
||||
virtual bool UnmuteAllPaths(const Client &client);
|
||||
virtual bool UnmuteAllTargetPaths(const Client &client);
|
||||
|
||||
virtual bool IsTargetPathMutingInverted(const Client &client);
|
||||
virtual bool InvertTargetPathMuting(const Client &client);
|
||||
|
||||
virtual bool MuteTargetPath(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type);
|
||||
virtual bool UnmuteTargetPath(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type);
|
||||
|
||||
virtual void RetainMessage(const es_message_t *msg);
|
||||
virtual void ReleaseMessage(const es_message_t *msg);
|
||||
|
||||
virtual bool RespondAuthResult(const Client &client, const Message &msg, es_auth_result_t result,
|
||||
bool cache);
|
||||
virtual bool RespondFlagsResult(const Client &client, const Message &msg, uint32_t allowed_flags,
|
||||
bool cache);
|
||||
|
||||
virtual bool MuteProcess(const Client &client, const audit_token_t *tok);
|
||||
|
||||
@@ -45,6 +61,12 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
|
||||
|
||||
virtual uint32_t ExecArgCount(const es_event_exec_t *event);
|
||||
virtual es_string_token_t ExecArg(const es_event_exec_t *event, uint32_t index);
|
||||
|
||||
virtual uint32_t ExecEnvCount(const es_event_exec_t *event);
|
||||
virtual es_string_token_t ExecEnv(const es_event_exec_t *event, uint32_t index);
|
||||
|
||||
virtual uint32_t ExecFDCount(const es_event_exec_t *event);
|
||||
virtual const es_fd_t *ExecFD(const es_event_exec_t *event, uint32_t index);
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user