mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d82e64aa5f | ||
|
|
a9c1c730be | ||
|
|
6c4362d8bb | ||
|
|
c1189493e8 | ||
|
|
aaa0d40841 | ||
|
|
a424c4afca | ||
|
|
2847397b66 | ||
|
|
ad8b4b6646 | ||
|
|
39ee9e7d48 | ||
|
|
3cccacc3fb | ||
|
|
6ed5bcd808 | ||
|
|
bcac65a23e | ||
|
|
03fcd0c906 | ||
|
|
d3b71a3ba8 | ||
|
|
9e124f4c51 | ||
|
|
cd719ccef4 | ||
|
|
dde42ee686 | ||
|
|
d144e27798 | ||
|
|
afc2c216b8 | ||
|
|
03d7556f22 | ||
|
|
020827b091 | ||
|
|
baa31a5db0 | ||
|
|
9ba7075596 | ||
|
|
5d08538639 | ||
|
|
e73bafb596 | ||
|
|
1e92d109a7 | ||
|
|
6a6aa6dce8 | ||
|
|
0715033d6a | ||
|
|
123d7a2d6a | ||
|
|
7b4d997589 | ||
|
|
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 |
@@ -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
|
||||
|
||||
37
.bazelrc
37
.bazelrc
@@ -3,17 +3,44 @@ build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
build --copt=-Werror
|
||||
build --copt=-Wall
|
||||
build --copt=-Wno-error=deprecated-declarations
|
||||
# Disable -Wunknown-warning-option because deprecated-non-prototype
|
||||
# isn't recognized on older SDKs
|
||||
build --copt=-Wno-unknown-warning-option
|
||||
build --copt=-Wno-error=deprecated-non-prototype
|
||||
build --per_file_copt=.*\.mm\$@-std=c++17
|
||||
build --cxxopt=-std=c++17
|
||||
|
||||
build --copt=-DSANTA_OPEN_SOURCE=1
|
||||
build --cxxopt=-DSANTA_OPEN_SOURCE=1
|
||||
|
||||
build:asan --strip=never
|
||||
build:asan --copt="-Wno-macro-redefined"
|
||||
build:asan --copt="-D_FORTIFY_SOURCE=0"
|
||||
build:asan --copt="-O1"
|
||||
build:asan --copt="-fno-omit-frame-pointer"
|
||||
# Many config options for sanitizers pulled from
|
||||
# https://github.com/protocolbuffers/protobuf/blob/main/.bazelrc
|
||||
build:san-common --strip=never
|
||||
build:san-common --copt="-Wno-macro-redefined"
|
||||
build:san-common --copt="-D_FORTIFY_SOURCE=0"
|
||||
build:san-common --copt="-O1"
|
||||
build:san-common --copt="-fno-omit-frame-pointer"
|
||||
|
||||
build:asan --config=san-common
|
||||
build:asan --copt="-fsanitize=address"
|
||||
build:asan --copt="-DADDRESS_SANITIZER"
|
||||
build:asan --linkopt="-fsanitize=address"
|
||||
build:asan --test_env="ASAN_OPTIONS=log_path=/tmp/san_out"
|
||||
|
||||
build: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, macos-13]
|
||||
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, macos-13]
|
||||
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
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
# Example NOTARIZATION_TOOL wrapper.
|
||||
|
||||
/usr/bin/xcrun altool --notarize-app "${2}" --primary-bundle-id "${4}" \
|
||||
-u "${NOTARIZATION_USERNAME}" -p "${NOTARIZATION_PASSWORD}"
|
||||
/usr/bin/xcrun notarytool submit "${2}" --wait \
|
||||
--apple-id "${NOTARIZATION_USERNAME}" --password "${NOTARIZATION_PASSWORD}"
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
# tool around the tool to use for notarization. The tool must take 2 flags:
|
||||
# --file
|
||||
# - pointing at a zip file containing the artifact to notarize
|
||||
# --primary-bundle-id
|
||||
# - specifying the CFBundleID of the artifact being notarized
|
||||
[[ -n "${NOTARIZATION_TOOL}" ]] || die "NOTARIZATION_TOOL unset"
|
||||
|
||||
# ARTIFACTS_DIR is a required environment variable pointing at a directory to
|
||||
@@ -92,7 +90,7 @@ for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
|
||||
echo "notarizing ${BN}"
|
||||
PBID=$(/usr/bin/defaults read "${ARTIFACT}/Contents/Info.plist" CFBundleIdentifier)
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip"
|
||||
done
|
||||
|
||||
# Staple the App.
|
||||
@@ -166,8 +164,7 @@ echo "verifying pkg signature"
|
||||
/usr/sbin/pkgutil --check-signature "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "bad pkg signature"
|
||||
|
||||
echo "notarizing pkg"
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" \
|
||||
--primary-bundle-id "com.google.santa"
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg"
|
||||
|
||||
echo "stapling pkg"
|
||||
/usr/bin/xcrun stapler staple "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "failed to staple pkg"
|
||||
@@ -179,7 +176,7 @@ echo "wrapping pkg in dmg"
|
||||
-srcfolder "${SCRATCH}/${RELEASE_NAME}/" "${DMG_PATH}" || die "failed to wrap pkg in dmg"
|
||||
|
||||
echo "notarizing dmg"
|
||||
"${NOTARIZATION_TOOL}" --file "${DMG_PATH}" --primary-bundle-id "com.google.santa"
|
||||
"${NOTARIZATION_TOOL}" --file "${DMG_PATH}"
|
||||
|
||||
echo "stapling dmg"
|
||||
/usr/bin/xcrun stapler staple "${DMG_PATH}" || die "failed to staple dmg"
|
||||
|
||||
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;
|
||||
;
|
||||
}
|
||||
|
||||
1
LICENSE
1
LICENSE
@@ -200,3 +200,4 @@
|
||||
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.
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -31,19 +31,57 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
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(
|
||||
@@ -74,11 +112,11 @@ objc_library(
|
||||
|
||||
objc_library(
|
||||
name = "SNTCachedDecision",
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
srcs = ["SNTCachedDecision.mm"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SantaVnode",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -86,11 +124,28 @@ objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
module_name = "santa_common_SNTDeviceEvent",
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTFileAccessEvent",
|
||||
srcs = ["SNTFileAccessEvent.m"],
|
||||
hdrs = ["SNTFileAccessEvent.h"],
|
||||
module_name = "santa_common_SNTFileAccessEvent",
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
"@MOLCertificate",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
textual_hdrs = ["SNTCommonEnums.h"],
|
||||
@@ -100,6 +155,10 @@ objc_library(
|
||||
name = "SNTConfigurator",
|
||||
srcs = ["SNTConfigurator.m"],
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
module_name = "santa_common_SNTConfigurator",
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
@@ -142,15 +201,6 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTCommon",
|
||||
hdrs = ["SNTCommon.h"],
|
||||
defines = [
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
@@ -176,6 +226,9 @@ objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSyncConstants",
|
||||
@@ -185,7 +238,10 @@ objc_library(
|
||||
santa_unit_test(
|
||||
name = "SNTRuleTest",
|
||||
srcs = ["SNTRuleTest.m"],
|
||||
deps = [":SNTRule"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -207,13 +263,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(
|
||||
@@ -287,11 +349,11 @@ objc_library(
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
":SantaVnode",
|
||||
"@MOLCertificate",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -357,6 +419,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
|
||||
@@ -59,6 +59,10 @@ class PrefixTree {
|
||||
}
|
||||
|
||||
std::optional<ValueT> LookupLongestMatchingPrefix(const char *input) {
|
||||
if (!input) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return LookupLongestMatchingPrefixLocked(input);
|
||||
}
|
||||
|
||||
@@ -191,12 +191,12 @@ using santa::common::PrefixTree;
|
||||
uint32_t count = 4096;
|
||||
auto t = new PrefixTree<int>(count * (uint32_t)[NSUUID UUID].UUIDString.length);
|
||||
|
||||
NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
|
||||
__block NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
[UUIDs addObject:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
__block BOOL stop = NO;
|
||||
__block _Atomic BOOL stop = NO;
|
||||
|
||||
// Create a bunch of background noise.
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
/// Return a URL generated from the EventDetailURL configuration key
|
||||
/// after replacing templates in the URL with values from the event.
|
||||
///
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event;
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url;
|
||||
|
||||
///
|
||||
/// Strip HTML from a string, replacing <br /> with newline.
|
||||
|
||||
@@ -109,14 +109,31 @@
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
|
||||
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
|
||||
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
|
||||
// The following "format strings" will be replaced in the URL, if they are present:
|
||||
//
|
||||
// %file_identifier% - The SHA-256 of the binary being executed.
|
||||
// %bundle_or_file_identifier% - The hash of the bundle containing this file or the file itself,
|
||||
// if no bundle hash is present.
|
||||
// %username% - The executing user's name.
|
||||
// %machine_id% - The configured machine ID for this host.
|
||||
// %hostname% - The machine's FQDN.
|
||||
// %uuid% - The machine's UUID.
|
||||
// %serial% - The machine's serial number.
|
||||
//
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *hostname = [SNTSystemInfo longHostname];
|
||||
NSString *uuid = [SNTSystemInfo hardwareUUID];
|
||||
NSString *serial = [SNTSystemInfo serialNumber];
|
||||
NSString *formatStr = config.eventDetailURL;
|
||||
|
||||
NSString *formatStr = url;
|
||||
if (!url.length) formatStr = config.eventDetailURL;
|
||||
if (!formatStr.length) return nil;
|
||||
if ([formatStr isEqualToString:@"null"]) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
|
||||
@@ -148,7 +165,9 @@
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%" withString:serial];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
NSURL *u = [NSURL URLWithString:formatStr];
|
||||
if (!u) LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
|
||||
return u;
|
||||
}
|
||||
|
||||
@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,10 +37,12 @@
|
||||
@property NSString *certCommonName;
|
||||
@property NSArray<MOLCertificate *> *certChain;
|
||||
@property NSString *teamID;
|
||||
@property NSString *signingID;
|
||||
|
||||
@property NSString *quarantineURL;
|
||||
|
||||
@property NSString *customMsg;
|
||||
@property NSString *customURL;
|
||||
@property BOOL silentBlock;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,37 @@
|
||||
/// The integer values are also stored in the database and so shouldn't be changed.
|
||||
///
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
SNTRuleTypeUnknown,
|
||||
typedef NS_ENUM(NSInteger, SNTAction) {
|
||||
SNTActionUnset,
|
||||
|
||||
SNTRuleTypeBinary = 1,
|
||||
SNTRuleTypeCertificate = 2,
|
||||
SNTRuleTypeTeamID = 3,
|
||||
// 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)
|
||||
|
||||
// Supported Rule Types
|
||||
//
|
||||
// Note: These enum values should be in order of decreasing precedence as
|
||||
// evaluated by Santa. When adding new enum values, leave some space so that
|
||||
// additional rules can be added without violating this. The ordering isn't
|
||||
// strictly necessary but improves readability and may preemptively prevent
|
||||
// issues should SQLite behavior change.
|
||||
typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
SNTRuleTypeUnknown = 0,
|
||||
|
||||
SNTRuleTypeBinary = 1000,
|
||||
SNTRuleTypeSigningID = 2000,
|
||||
SNTRuleTypeCertificate = 3000,
|
||||
SNTRuleTypeTeamID = 4000,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
@@ -46,32 +71,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) {
|
||||
@@ -94,6 +121,7 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeJSON,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
@@ -112,12 +140,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";
|
||||
|
||||
@@ -194,6 +194,13 @@
|
||||
///
|
||||
@property(readonly, nonatomic) SNTEventLogType eventLogType;
|
||||
|
||||
///
|
||||
/// Returns the raw value of the EventLogType configuration key instead of being
|
||||
/// converted to the SNTEventLogType enum. If the key is not set, the default log
|
||||
/// type is returned.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogTypeRaw;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to Filelog, eventLogPath will provide the path to save logs.
|
||||
/// Defaults to /var/db/santa/santa.log.
|
||||
@@ -238,6 +245,33 @@
|
||||
///
|
||||
@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
|
||||
/// has been overriden, this is the host's UUID.
|
||||
@@ -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.
|
||||
@@ -348,6 +390,27 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
|
||||
|
||||
///
|
||||
/// Extra headers to include in all requests made during syncing.
|
||||
/// Keys and values must all be strings, any other type will be silently ignored.
|
||||
/// Some headers cannot be set through this key, including:
|
||||
///
|
||||
/// * Content-Encoding
|
||||
/// * Content-Length
|
||||
/// * Content-Type
|
||||
/// * Connection
|
||||
/// * Host
|
||||
/// * Proxy-Authenticate
|
||||
/// * Proxy-Authorization
|
||||
/// * WWW-Authenticate
|
||||
///
|
||||
/// The header "Authorization" is also documented by Apple to be one that will
|
||||
/// be ignored but this is not really the case, at least at present. If you
|
||||
/// are able to use a different header for this that would be safest but if not
|
||||
/// using Authorization /should/ be fine.
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *syncExtraHeaders;
|
||||
|
||||
///
|
||||
/// The machine owner.
|
||||
///
|
||||
@@ -368,6 +431,8 @@
|
||||
///
|
||||
@property(nonatomic) BOOL syncCleanRequired;
|
||||
|
||||
#pragma mark - USB Settings
|
||||
|
||||
///
|
||||
/// USB Mount Blocking. Defaults to false.
|
||||
///
|
||||
@@ -478,6 +543,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";
|
||||
|
||||
@@ -52,6 +57,7 @@ static NSString *const kMobileConfigDomain = @"com.google.santa";
|
||||
static NSString *const kStaticRules = @"StaticRules";
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kSyncProxyConfigKey = @"SyncProxyConfiguration";
|
||||
static NSString *const kSyncExtraHeadersKey = @"SyncExtraHeaders";
|
||||
static NSString *const kSyncEnableCleanSyncEventUpload = @"SyncEnableCleanSyncEventUpload";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
@@ -68,6 +74,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";
|
||||
@@ -93,14 +100,17 @@ static NSString *const kSpoolDirectoryFileSizeThresholdKB = @"SpoolDirectoryFile
|
||||
static NSString *const kSpoolDirectorySizeThresholdMB = @"SpoolDirectorySizeThresholdMB";
|
||||
static NSString *const kSpoolDirectoryEventMaxFlushTimeSec = @"SpoolDirectoryEventMaxFlushTimeSec";
|
||||
|
||||
static NSString *const kFileAccessPolicy = @"FileAccessPolicy";
|
||||
static NSString *const kFileAccessPolicyPlist = @"FileAccessPolicyPlist";
|
||||
static NSString *const kFileAccessPolicyUpdateIntervalSec = @"FileAccessPolicyUpdateIntervalSec";
|
||||
|
||||
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
|
||||
|
||||
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 +130,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";
|
||||
@@ -173,6 +182,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kEnableSilentModeKey : number,
|
||||
kEnableSilentTTYModeKey : number,
|
||||
kAboutTextKey : string,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
@@ -185,11 +195,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncEnableCleanSyncEventUpload : number,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kSyncExtraHeadersKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kClientContentEncoding : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
@@ -204,11 +217,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kSpoolDirectoryFileSizeThresholdKB : number,
|
||||
kSpoolDirectorySizeThresholdMB : number,
|
||||
kSpoolDirectoryEventMaxFlushTimeSec : number,
|
||||
kFileAccessPolicy : dictionary,
|
||||
kFileAccessPolicyPlist : string,
|
||||
kFileAccessPolicyUpdateIntervalSec : number,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
kEnableDebugLogging : number,
|
||||
kEnableBackwardsCompatibleContentEncoding : number,
|
||||
kFCMProject : string,
|
||||
kFCMEntity : string,
|
||||
kFCMAPIKey : string,
|
||||
@@ -233,7 +248,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, ^{
|
||||
@@ -299,6 +317,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncExtraHeaders {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableCleanSyncEventUpload {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -407,6 +433,18 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicy {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyPlist {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyUpdateIntervalSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -435,10 +473,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -604,6 +638,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return self.configState[kSyncProxyConfigKey];
|
||||
}
|
||||
|
||||
- (NSDictionary *)syncExtraHeaders {
|
||||
return self.configState[kSyncExtraHeadersKey];
|
||||
}
|
||||
|
||||
- (BOOL)enablePageZeroProtection {
|
||||
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
|
||||
return number ? [number boolValue] : YES;
|
||||
@@ -619,6 +657,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];
|
||||
}
|
||||
@@ -682,6 +725,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];
|
||||
}
|
||||
@@ -752,6 +809,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return SNTEventLogTypeSyslog;
|
||||
} else if ([logType isEqualToString:@"null"]) {
|
||||
return SNTEventLogTypeNull;
|
||||
} else if ([logType isEqualToString:@"json"]) {
|
||||
return SNTEventLogTypeJSON;
|
||||
} else if ([logType isEqualToString:@"file"]) {
|
||||
return SNTEventLogTypeFilelog;
|
||||
} else {
|
||||
@@ -759,6 +818,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)eventLogTypeRaw {
|
||||
return self.configState[kEventLogType] ?: @"file";
|
||||
}
|
||||
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
@@ -785,6 +848,25 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
: 15.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 {
|
||||
NSNumber *number = self.configState[kEnableMachineIDDecoration];
|
||||
return number ? [number boolValue] : NO;
|
||||
@@ -832,11 +914,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];
|
||||
}
|
||||
@@ -992,6 +1069,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;
|
||||
}
|
||||
|
||||
@@ -1008,12 +1097,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];
|
||||
}
|
||||
|
||||
///
|
||||
@@ -1036,6 +1163,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];
|
||||
|
||||
83
Source/common/SNTFileAccessEvent.h
Normal file
83
Source/common/SNTFileAccessEvent.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/// 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
///
|
||||
@interface SNTFileAccessEvent : NSObject <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// The watched path that was accessed
|
||||
///
|
||||
@property NSString *accessedPath;
|
||||
|
||||
///
|
||||
/// The rule version and name that were violated
|
||||
///
|
||||
@property NSString *ruleVersion;
|
||||
@property NSString *ruleName;
|
||||
|
||||
///
|
||||
/// The SHA256 of the process that accessed the path
|
||||
///
|
||||
@property NSString *fileSHA256;
|
||||
|
||||
///
|
||||
/// The path of the process that accessed the watched path
|
||||
///
|
||||
@property NSString *filePath;
|
||||
|
||||
///
|
||||
/// If the process is part of a bundle, the name of the application
|
||||
///
|
||||
@property NSString *application;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Team ID if present in the signature information.
|
||||
///
|
||||
@property NSString *teamID;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Signing ID if present in the signature information.
|
||||
///
|
||||
@property NSString *signingID;
|
||||
|
||||
///
|
||||
/// The user who executed the binary.
|
||||
///
|
||||
@property NSString *executingUser;
|
||||
|
||||
///
|
||||
/// The process ID of the binary being executed.
|
||||
///
|
||||
@property NSNumber *pid;
|
||||
|
||||
///
|
||||
/// The parent process ID of the binary being executed.
|
||||
///
|
||||
@property NSNumber *ppid;
|
||||
|
||||
///
|
||||
/// The name of the parent process.
|
||||
///
|
||||
@property NSString *parentName;
|
||||
|
||||
// TODO(mlw): Store signing chain info
|
||||
// @property NSArray<MOLCertificate*> *signingChain;
|
||||
|
||||
@end
|
||||
79
Source/common/SNTFileAccessEvent.m
Normal file
79
Source/common/SNTFileAccessEvent.m
Normal file
@@ -0,0 +1,79 @@
|
||||
/// 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.
|
||||
|
||||
#import "Source/common/SNTFileAccessEvent.h"
|
||||
|
||||
@implementation SNTFileAccessEvent
|
||||
|
||||
#define ENCODE(o) \
|
||||
do { \
|
||||
if (self.o) { \
|
||||
[coder encodeObject:self.o forKey:@(#o)]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define DECODE(o, c) \
|
||||
do { \
|
||||
_##o = [decoder decodeObjectOfClass:[c class] forKey:@(#o)]; \
|
||||
} while (0)
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(accessedPath);
|
||||
ENCODE(ruleVersion);
|
||||
ENCODE(ruleName);
|
||||
ENCODE(fileSHA256);
|
||||
ENCODE(filePath);
|
||||
ENCODE(application);
|
||||
ENCODE(teamID);
|
||||
ENCODE(teamID);
|
||||
ENCODE(pid);
|
||||
ENCODE(ppid);
|
||||
ENCODE(parentName);
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
DECODE(accessedPath, NSString);
|
||||
DECODE(ruleVersion, NSString);
|
||||
DECODE(ruleName, NSString);
|
||||
DECODE(fileSHA256, NSString);
|
||||
DECODE(filePath, NSString);
|
||||
DECODE(application, NSString);
|
||||
DECODE(teamID, NSString);
|
||||
DECODE(teamID, NSString);
|
||||
DECODE(pid, NSNumber);
|
||||
DECODE(ppid, NSNumber);
|
||||
DECODE(parentName, NSString);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString
|
||||
stringWithFormat:@"SNTFileAccessEvent: Accessed: %@, By: %@", self.accessedPath, self.filePath];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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"];
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
///
|
||||
@property(copy) NSString *customMsg;
|
||||
|
||||
///
|
||||
/// A custom URL to take the user to when this binary is blocked from executing.
|
||||
///
|
||||
@property(copy) NSString *customURL;
|
||||
|
||||
///
|
||||
/// The time when this rule was last retrieved from the rules database, if rule is transitive.
|
||||
/// Stored as number of seconds since 00:00:00 UTC on 1 January 2001.
|
||||
|
||||
@@ -13,8 +13,15 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
#include <CommonCrypto/CommonCrypto.h>
|
||||
#include <os/base.h>
|
||||
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
|
||||
// https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/
|
||||
static const NSUInteger kExpectedTeamIDLength = 10;
|
||||
|
||||
@interface SNTRule ()
|
||||
@property(readwrite) NSUInteger timestamp;
|
||||
@end
|
||||
@@ -28,6 +35,79 @@
|
||||
timestamp:(NSUInteger)timestamp {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (identifier.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSCharacterSet *nonHex =
|
||||
[[NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdef"] invertedSet];
|
||||
NSCharacterSet *nonUppercaseAlphaNumeric = [[NSCharacterSet
|
||||
characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"] invertedSet];
|
||||
|
||||
switch (type) {
|
||||
case SNTRuleTypeBinary: OS_FALLTHROUGH;
|
||||
case SNTRuleTypeCertificate: {
|
||||
// For binary and certificate rules, force the hash identifier to be lowercase hex.
|
||||
identifier = [identifier lowercaseString];
|
||||
|
||||
identifier = [identifier stringByTrimmingCharactersInSet:nonHex];
|
||||
if (identifier.length != (CC_SHA256_DIGEST_LENGTH * 2)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SNTRuleTypeTeamID: {
|
||||
// TeamIDs are always [0-9A-Z], so enforce that the identifier is uppercase
|
||||
identifier =
|
||||
[[identifier uppercaseString] stringByTrimmingCharactersInSet:nonUppercaseAlphaNumeric];
|
||||
if (identifier.length != kExpectedTeamIDLength) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SNTRuleTypeSigningID: {
|
||||
// SigningID rules are a combination of `TeamID:SigningID`. The TeamID should
|
||||
// be forced to be uppercase, but because very loose rules exist for SigningIDs,
|
||||
// their case will be kept as-is. However, platform binaries are expected to
|
||||
// have the hardcoded string "platform" as the team ID and the case will be left
|
||||
// as is.
|
||||
NSArray *sidComponents = [identifier componentsSeparatedByString:@":"];
|
||||
if (!sidComponents || sidComponents.count < 2) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// The first component is the TeamID
|
||||
NSString *teamID = sidComponents[0];
|
||||
|
||||
if (![teamID isEqualToString:@"platform"]) {
|
||||
teamID =
|
||||
[[teamID uppercaseString] stringByTrimmingCharactersInSet:nonUppercaseAlphaNumeric];
|
||||
if (teamID.length != kExpectedTeamIDLength) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the components are the Signing ID since ":" a legal character.
|
||||
// Join all but the last element of the components to rebuild the SigningID.
|
||||
NSString *signingID = [[sidComponents
|
||||
subarrayWithRange:NSMakeRange(1, sidComponents.count - 1)] componentsJoinedByString:@":"];
|
||||
if (signingID.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
identifier = [NSString stringWithFormat:@"%@:%@", teamID, signingID];
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_identifier = identifier;
|
||||
_state = state;
|
||||
_type = type;
|
||||
@@ -55,52 +135,61 @@
|
||||
- (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;
|
||||
}
|
||||
|
||||
NSString *customURL = dict[kRuleCustomURL];
|
||||
if (![customURL isKindOfClass:[NSString class]] || customURL.length == 0) {
|
||||
customURL = nil;
|
||||
}
|
||||
|
||||
SNTRule *r = [self initWithIdentifier:identifier state:state type:type customMsg:customMsg];
|
||||
r.customURL = customURL;
|
||||
return r;
|
||||
}
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
@@ -120,6 +209,7 @@
|
||||
ENCODE(@(self.state), @"state");
|
||||
ENCODE(@(self.type), @"type");
|
||||
ENCODE(self.customMsg, @"custommsg");
|
||||
ENCODE(self.customURL, @"customurl");
|
||||
ENCODE(@(self.timestamp), @"timestamp");
|
||||
}
|
||||
|
||||
@@ -130,6 +220,7 @@
|
||||
_state = [DECODE(NSNumber, @"state") intValue];
|
||||
_type = [DECODE(NSNumber, @"type") intValue];
|
||||
_customMsg = DECODE(NSString, @"custommsg");
|
||||
_customURL = DECODE(NSString, @"customurl");
|
||||
_timestamp = [DECODE(NSNumber, @"timestamp") unsignedIntegerValue];
|
||||
}
|
||||
return self;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#include "Source/common/SNTCommonEnums.h"
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
@@ -25,66 +26,167 @@
|
||||
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);
|
||||
|
||||
// Ensure a Binary and Certificate rules properly convert identifiers to lowercase.
|
||||
for (NSString *ruleType in @[ @"BINARY", @"CERTIFICATE" ]) {
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"B7C1E3FD640C5F211C89B02C2C6122F78CE322AA5C56EB0BB54BC422A8F8B670",
|
||||
@"policy" : @"BLOCKLIST",
|
||||
@"rule_type" : ruleType,
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier,
|
||||
@"b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670");
|
||||
}
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"identifier" : @"ABCDEFGHIJ",
|
||||
@"policy" : @"SILENT_BLOCKLIST",
|
||||
@"rule_type" : @"TEAMID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqualObjects(sut.identifier, @"ABCDEFGHIJ");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
|
||||
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);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"identifier" : @"ABCDEFGHIJ",
|
||||
@"policy" : @"REMOVE",
|
||||
@"rule_type" : @"TEAMID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqualObjects(sut.identifier, @"ABCDEFGHIJ");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateRemove);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"identifier" : @"ABCDEFGHIJ",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"TEAMID",
|
||||
@"custom_msg" : @"A custom block message",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqualObjects(sut.identifier, @"ABCDEFGHIJ");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateAllow);
|
||||
XCTAssertEqualObjects(sut.customMsg, @"A custom block message");
|
||||
|
||||
// TeamIDs must be 10 chars in length
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"A",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"TEAMID",
|
||||
}];
|
||||
XCTAssertNil(sut);
|
||||
|
||||
// TeamIDs must be only alphanumeric chars
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"ßßßßßßßßßß",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"TEAMID",
|
||||
}];
|
||||
XCTAssertNil(sut);
|
||||
|
||||
// TeamIDs are converted to uppercase
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"abcdefghij",
|
||||
@"policy" : @"REMOVE",
|
||||
@"rule_type" : @"TEAMID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"ABCDEFGHIJ");
|
||||
|
||||
// SigningID tests
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"ABCDEFGHIJ:com.example",
|
||||
@"policy" : @"REMOVE",
|
||||
@"rule_type" : @"SIGNINGID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"ABCDEFGHIJ:com.example");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeSigningID);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateRemove);
|
||||
|
||||
// Invalid SingingID tests:
|
||||
for (NSString *ident in @[
|
||||
@":com.example", // missing team ID
|
||||
@"ABCDEFGHIJ:", // missing signing ID
|
||||
@"ABC:com.example", // Invalid team id
|
||||
@":", // missing team and signing IDs
|
||||
@"", // empty string
|
||||
]) {
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : ident,
|
||||
@"policy" : @"REMOVE",
|
||||
@"rule_type" : @"SIGNINGID",
|
||||
}];
|
||||
XCTAssertNil(sut);
|
||||
}
|
||||
|
||||
// Signing ID with lower team ID has case fixed up
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"abcdefghij:com.example",
|
||||
@"policy" : @"REMOVE",
|
||||
@"rule_type" : @"SIGNINGID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"ABCDEFGHIJ:com.example");
|
||||
|
||||
// Signing ID with lower platform team ID is left alone
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"platform:com.example",
|
||||
@"policy" : @"REMOVE",
|
||||
@"rule_type" : @"SIGNINGID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"platform:com.example");
|
||||
|
||||
// Signing ID can contain the TID:SID delimiter character (":")
|
||||
for (NSString *ident in @[
|
||||
@"ABCDEFGHIJ:com:",
|
||||
@"ABCDEFGHIJ:com:example",
|
||||
@"ABCDEFGHIJ::",
|
||||
@"ABCDEFGHIJ:com:example:with:more:components:",
|
||||
]) {
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : ident,
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"SIGNINGID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, ident);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testInitWithDictionaryInvalid {
|
||||
@@ -94,12 +196,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,7 +122,9 @@ 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 kRuleCustomURL;
|
||||
extern NSString *const kCursor;
|
||||
|
||||
extern NSString *const kBackoffInterval;
|
||||
|
||||
@@ -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,7 +123,9 @@ 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 kRuleCustomURL = @"custom_url";
|
||||
NSString *const kCursor = @"cursor";
|
||||
|
||||
NSString *const kBackoffInterval = @"backoff";
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID
|
||||
signingID:(NSString *)signingID
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
|
||||
///
|
||||
|
||||
@@ -17,13 +17,18 @@
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
@class SNTDeviceEvent;
|
||||
@class SNTFileAccessEvent;
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by SantaGUI and utilized by santad
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event
|
||||
withCustomMessage:(NSString *)message
|
||||
andCustomURL:(NSString *)url;
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
|
||||
withCustomMessage:(NSString *)message API_AVAILABLE(macos(13.0));
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
43
Source/common/String.h
Normal file
43
Source/common/String.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/// 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]);
|
||||
}
|
||||
|
||||
static inline NSString *StringToNSString(const std::string &str) {
|
||||
return [NSString stringWithUTF8String:str.c_str()];
|
||||
}
|
||||
|
||||
static inline NSString *StringToNSString(const char *str) {
|
||||
return [NSString stringWithUTF8String:str];
|
||||
}
|
||||
|
||||
} // 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),
|
||||
};
|
||||
}
|
||||
@@ -38,23 +38,29 @@
|
||||
// Pretty print C++ string match errors
|
||||
#define XCTAssertCppStringEqual(got, want) XCTAssertCStringEqual((got).c_str(), (want).c_str())
|
||||
|
||||
// Note: Delta between local formatter and the one run on Github. Disable for now.
|
||||
// clang-format off
|
||||
#define XCTAssertSemaTrue(s, sec, m) \
|
||||
XCTAssertEqual( \
|
||||
0, dispatch_semaphore_wait((s), dispatch_time(DISPATCH_TIME_NOW, (sec)*NSEC_PER_SEC)), m)
|
||||
0, dispatch_semaphore_wait((s), dispatch_time(DISPATCH_TIME_NOW, (sec) * NSEC_PER_SEC)), m)
|
||||
// clang-format on
|
||||
|
||||
// Helper to ensure at least `ms` milliseconds are slept, even if the sleep
|
||||
// function returns early due to interrupts.
|
||||
void SleepMS(long ms);
|
||||
|
||||
enum class ActionType {
|
||||
Auth,
|
||||
Notify,
|
||||
};
|
||||
// Helper to construct strings of a given length
|
||||
NSString *RepeatedString(NSString *str, NSUInteger len);
|
||||
|
||||
//
|
||||
// Helpers to construct various ES structs
|
||||
//
|
||||
|
||||
enum class ActionType {
|
||||
Auth,
|
||||
Notify,
|
||||
};
|
||||
|
||||
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver);
|
||||
|
||||
/// Construct a `struct stat` buffer with each member having a unique value.
|
||||
@@ -64,7 +70,7 @@ audit_token_t MakeAuditToken(pid_t pid, pid_t pidver);
|
||||
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_file_t MakeESFile(const char *path, struct stat sb = MakeStat());
|
||||
es_process_t MakeESProcess(es_file_t *file, audit_token_t tok = {}, audit_token_t parent_tok = {});
|
||||
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc,
|
||||
ActionType action_type = ActionType::Notify,
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#include <mach/mach_time.h>
|
||||
#include <time.h>
|
||||
#include <uuid/uuid.h>
|
||||
#include "Source/common/SystemResources.h"
|
||||
|
||||
NSString *RepeatedString(NSString *str, NSUInteger len) {
|
||||
return [@"" stringByPaddingToLength:len withString:str startingAtIndex:0];
|
||||
}
|
||||
|
||||
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver) {
|
||||
return audit_token_t{
|
||||
@@ -86,24 +91,6 @@ es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t par
|
||||
};
|
||||
}
|
||||
|
||||
static uint64_t AddMillisToMachTime(uint64_t ms, uint64_t machTime) {
|
||||
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, *)) {
|
||||
@@ -122,7 +109,7 @@ uint32_t MaxSupportedESMessageVersionForCurrentOS() {
|
||||
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType action_type,
|
||||
uint64_t future_deadline_ms) {
|
||||
es_message_t es_msg = {
|
||||
.deadline = AddMillisToMachTime(future_deadline_ms, mach_absolute_time()),
|
||||
.deadline = AddNanosecondsToMachTime(future_deadline_ms * NSEC_PER_MSEC, mach_absolute_time()),
|
||||
.process = proc,
|
||||
.action_type =
|
||||
(action_type == ActionType::Notify) ? ES_ACTION_TYPE_NOTIFY : ES_ACTION_TYPE_AUTH,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
//
|
||||
// !!! WARNING !!!
|
||||
// This proto is for demonstration purposes only and will be changing.
|
||||
// Do not rely on this format.
|
||||
//
|
||||
// Important: This schema is currently in BETA
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
@@ -235,10 +231,10 @@ message Execution {
|
||||
optional FileInfo working_directory = 4;
|
||||
|
||||
// List of process arguments
|
||||
repeated string args = 5;
|
||||
repeated bytes args = 5;
|
||||
|
||||
// List of environment variables
|
||||
repeated string envs = 6;
|
||||
repeated bytes envs = 6;
|
||||
|
||||
// List of file descriptors
|
||||
repeated FileDescriptor fds = 7;
|
||||
@@ -266,6 +262,7 @@ message Execution {
|
||||
REASON_TRANSITIVE = 8;
|
||||
REASON_LONG_PATH = 9;
|
||||
REASON_NOT_RUNNING = 10;
|
||||
REASON_SIGNING_ID = 11;
|
||||
}
|
||||
optional Reason reason = 10;
|
||||
|
||||
@@ -464,6 +461,45 @@ message Allowlist {
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
// Information about access to a watched path
|
||||
message FileAccess {
|
||||
// The process that attempted to access the watched path
|
||||
optional ProcessInfo instigator = 1;
|
||||
|
||||
// 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
|
||||
@@ -489,6 +525,7 @@ message SantaMessage {
|
||||
Disk disk = 18;
|
||||
Bundle bundle = 19;
|
||||
Allowlist allowlist = 20;
|
||||
FileAccess file_access = 21;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,36 @@ 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",
|
||||
],
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "SNTFileAccessMessageWindowView",
|
||||
srcs = [
|
||||
"SNTFileAccessMessageWindowView.swift",
|
||||
],
|
||||
generates_header = 1,
|
||||
deps = [
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SantaGUI_lib",
|
||||
srcs = [
|
||||
@@ -24,8 +55,8 @@ objc_library(
|
||||
"SNTBinaryMessageWindowController.m",
|
||||
"SNTDeviceMessageWindowController.h",
|
||||
"SNTDeviceMessageWindowController.m",
|
||||
"SNTMessageWindow.h",
|
||||
"SNTMessageWindow.m",
|
||||
"SNTFileAccessMessageWindowController.h",
|
||||
"SNTFileAccessMessageWindowController.m",
|
||||
"SNTMessageWindowController.h",
|
||||
"SNTMessageWindowController.m",
|
||||
"SNTNotificationManager.h",
|
||||
@@ -36,8 +67,6 @@ objc_library(
|
||||
"SNTNotificationManager.h",
|
||||
],
|
||||
data = [
|
||||
"Resources/AboutWindow.xib",
|
||||
"Resources/DeviceMessageWindow.xib",
|
||||
"Resources/MessageWindow.xib",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
@@ -47,9 +76,13 @@ objc_library(
|
||||
"UserNotifications",
|
||||
],
|
||||
deps = [
|
||||
":SNTAboutWindowView",
|
||||
":SNTDeviceMessageWindowView",
|
||||
":SNTFileAccessMessageWindowView",
|
||||
"//Source/common:SNTBlockMessage_SantaGUI",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTStrengthify",
|
||||
@@ -85,7 +118,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;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
///
|
||||
@interface SNTBinaryMessageWindowController : SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event
|
||||
customMsg:(NSString *)message
|
||||
customURL:(NSString *)url;
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender;
|
||||
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash;
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
#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
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
/// The custom URL to use for this event
|
||||
@property(copy) NSString *customURL;
|
||||
|
||||
/// A 'friendly' string representing the certificate information
|
||||
@property(readonly, nonatomic) NSString *publisherInfo;
|
||||
|
||||
@@ -40,11 +42,14 @@
|
||||
|
||||
@implementation SNTBinaryMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event
|
||||
customMsg:(NSString *)message
|
||||
customURL:(NSString *)url {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = message;
|
||||
_customURL = url;
|
||||
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
|
||||
[_progress addObserver:self
|
||||
forKeyPath:@"fractionCompleted"
|
||||
@@ -75,7 +80,9 @@
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event customURL:self.customURL];
|
||||
|
||||
if (!url) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
@@ -121,7 +128,8 @@
|
||||
}
|
||||
|
||||
- (IBAction)openEventDetails:(id)sender {
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event customURL:self.customURL];
|
||||
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
90
Source/gui/SNTDeviceMessageWindowView.swift
Normal file
90
Source/gui/SNTDeviceMessageWindowView.swift
Normal file
@@ -0,0 +1,90 @@
|
||||
/// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
35
Source/gui/SNTFileAccessMessageWindowController.h
Normal file
35
Source/gui/SNTFileAccessMessageWindowController.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.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SNTFileAccessEvent;
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
API_AVAILABLE(macos(13.0))
|
||||
@interface SNTFileAccessMessageWindowController : SNTMessageWindowController <NSWindowDelegate>
|
||||
|
||||
- (instancetype)initWithEvent:(SNTFileAccessEvent *)event message:(nullable NSString *)message;
|
||||
|
||||
@property(readonly) SNTFileAccessEvent *event;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
79
Source/gui/SNTFileAccessMessageWindowController.m
Normal file
79
Source/gui/SNTFileAccessMessageWindowController.m
Normal file
@@ -0,0 +1,79 @@
|
||||
/// 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.
|
||||
|
||||
#import "Source/gui/SNTFileAccessMessageWindowController.h"
|
||||
#import "Source/gui/SNTFileAccessMessageWindowView-Swift.h"
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTFileAccessEvent.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
@interface SNTFileAccessMessageWindowController ()
|
||||
@property NSString *customMessage;
|
||||
@property SNTFileAccessEvent *event;
|
||||
@end
|
||||
|
||||
@implementation SNTFileAccessMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTFileAccessEvent *)event message:(nullable NSString *)message {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_customMessage = message;
|
||||
_event = event;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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 =
|
||||
[SNTFileAccessMessageWindowViewFactory 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 {
|
||||
return [SNTBlockMessage formatMessage:self.customMessage];
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
// TODO(mlw): This is not the final form. As this feature is expanded this
|
||||
// hash will need to be revisted to ensure it meets our needs.
|
||||
return [NSString stringWithFormat:@"%@|%@|%d", self.event.ruleName, self.event.ruleVersion,
|
||||
[self.event.pid intValue]];
|
||||
}
|
||||
|
||||
@end
|
||||
158
Source/gui/SNTFileAccessMessageWindowView.swift
Normal file
158
Source/gui/SNTFileAccessMessageWindowView.swift
Normal file
@@ -0,0 +1,158 @@
|
||||
/// 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.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
import santa_common_SNTFileAccessEvent
|
||||
|
||||
@available(macOS 13, *)
|
||||
@objc public class SNTFileAccessMessageWindowViewFactory : NSObject {
|
||||
@objc public static func createWith(window: NSWindow, event: SNTFileAccessEvent, customMsg: NSAttributedString?) -> NSViewController {
|
||||
return NSHostingController(rootView:SNTFileAccessMessageWindowView(window:window, event:event, customMsg:customMsg)
|
||||
.frame(width:800, height:600))
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
struct Property : View {
|
||||
var lbl: String
|
||||
var val: String
|
||||
|
||||
var body: some View {
|
||||
let width: CGFloat? = 150
|
||||
|
||||
HStack(spacing: 5) {
|
||||
Text(lbl + ":")
|
||||
.frame(width: width, alignment: .trailing)
|
||||
.lineLimit(1)
|
||||
.font(.system(size: 12, weight: .bold))
|
||||
.padding(Edge.Set.horizontal, 10)
|
||||
|
||||
Text(val)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
struct Event: View {
|
||||
let e: SNTFileAccessEvent
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing:10) {
|
||||
Property(lbl: "Path Accessed", val: e.accessedPath)
|
||||
Property(lbl: "Rule Name", val: e.ruleName)
|
||||
Property(lbl: "Rule Version", val: e.ruleVersion)
|
||||
|
||||
Divider()
|
||||
.frame(width: 700)
|
||||
|
||||
if let app = e.application {
|
||||
Property(lbl: "Application", val: app)
|
||||
}
|
||||
|
||||
Property(lbl: "Name", val: (e.filePath as NSString).lastPathComponent)
|
||||
Property(lbl: "Path", val: e.filePath)
|
||||
Property(lbl: "Identifier", val: e.fileSHA256)
|
||||
Property(lbl: "Parent", val: e.parentName + " (" + e.ppid.stringValue + ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
struct SNTFileAccessMessageWindowView: View {
|
||||
let window: NSWindow?
|
||||
let event: SNTFileAccessEvent?
|
||||
let customMsg: NSAttributedString?
|
||||
|
||||
@State private var checked = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing:20.0) {
|
||||
Spacer()
|
||||
Text("Santa").font(Font.custom("HelveticaNeue-UltraLight", size: 34.0))
|
||||
|
||||
if let msg = customMsg {
|
||||
Text(AttributedString(msg)).multilineTextAlignment(.center).padding(15.0)
|
||||
} else {
|
||||
Text("Access to a protected resource was denied.").multilineTextAlignment(.center).padding(15.0)
|
||||
}
|
||||
|
||||
Event(e: event!)
|
||||
|
||||
Toggle(isOn: $checked) {
|
||||
Text("Prevent future notifications for this application for a day")
|
||||
.font(Font.system(size: 11.0));
|
||||
}
|
||||
|
||||
VStack(spacing:15) {
|
||||
Button(action: openButton, label: {
|
||||
Text("Open Event Info...").frame(maxWidth:.infinity)
|
||||
})
|
||||
Button(action: dismissButton, label: {
|
||||
Text("Dismiss").frame(maxWidth:.infinity)
|
||||
})
|
||||
.keyboardShortcut(.return)
|
||||
}.frame(width: 220)
|
||||
|
||||
Spacer()
|
||||
|
||||
}.frame(maxWidth:800.0).fixedSize()
|
||||
}
|
||||
|
||||
func publisherInfo() {
|
||||
// TODO(mlw): Will hook up in a separate PR
|
||||
print("showing publisher popup...")
|
||||
}
|
||||
|
||||
func openButton() {
|
||||
// TODO(mlw): Will hook up in a separate PR
|
||||
print("opening event info...")
|
||||
}
|
||||
|
||||
func dismissButton() {
|
||||
window?.close()
|
||||
print("close window")
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
func testFileAccessEvent() -> SNTFileAccessEvent {
|
||||
let faaEvent = SNTFileAccessEvent()
|
||||
|
||||
faaEvent.accessedPath = "/accessed/path"
|
||||
faaEvent.ruleVersion = "watched_path.v1"
|
||||
faaEvent.ruleName = "watched_path"
|
||||
faaEvent.fileSHA256 = "b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670"
|
||||
faaEvent.filePath = "/Applications/gShoe.app/Contents/MacOS/gShoe"
|
||||
faaEvent.application = "gShoe"
|
||||
faaEvent.teamID = "EQHXZ8M8AV"
|
||||
faaEvent.signingID = "com.google.gShoe"
|
||||
faaEvent.executingUser = "nobody"
|
||||
faaEvent.pid = 456
|
||||
faaEvent.ppid = 123
|
||||
faaEvent.parentName = "gLauncher"
|
||||
|
||||
return faaEvent
|
||||
}
|
||||
|
||||
// Enable previews in Xcode.
|
||||
@available(macOS 13, *)
|
||||
struct SNTFileAccessMessageWindowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SNTFileAccessMessageWindowView(window: nil, event: testFileAccessEvent(), 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;
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/gui/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/gui/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
///
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/gui/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/gui/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/gui/SNTFileAccessMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
@@ -169,7 +172,8 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
[dc postNotificationName:@"com.google.santa.notification.blockedeexecution"
|
||||
object:@"com.google.santa"
|
||||
userInfo:userInfo];
|
||||
userInfo:userInfo
|
||||
deliverImmediately:YES];
|
||||
}
|
||||
|
||||
- (void)showQueuedWindow {
|
||||
@@ -319,14 +323,16 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[un addNotificationRequest:req withCompletionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event
|
||||
withCustomMessage:(NSString *)message
|
||||
andCustomURL:(NSString *)url {
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
|
||||
SNTBinaryMessageWindowController *pendingMsg =
|
||||
[[SNTBinaryMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
[[SNTBinaryMessageWindowController alloc] initWithEvent:event customMsg:message customURL:url];
|
||||
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
@@ -342,6 +348,19 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
|
||||
withCustomMessage:(NSString *)message API_AVAILABLE(macos(13.0)) {
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
|
||||
SNTFileAccessMessageWindowController *pendingMsg =
|
||||
[[SNTFileAccessMessageWindowController alloc] initWithEvent:event message:message];
|
||||
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
#pragma mark SNTBundleNotifierXPC protocol methods
|
||||
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
id dncMock = OCMClassMock([NSDistributedNotificationCenter class]);
|
||||
OCMStub([dncMock defaultCenter]).andReturn(dncMock);
|
||||
|
||||
[sut postBlockNotification:ev withCustomMessage:@""];
|
||||
[sut postBlockNotification:ev withCustomMessage:@"" andCustomURL:@""];
|
||||
|
||||
OCMVerify([dncMock postNotificationName:@"com.google.santa.notification.blockedeexecution"
|
||||
object:@"com.google.santa"
|
||||
@@ -68,7 +68,8 @@
|
||||
XCTAssertEqualObjects(userInfo[@"ppid"], @1);
|
||||
XCTAssertEqualObjects(userInfo[@"execution_time"], @1660221048);
|
||||
return YES;
|
||||
}]]);
|
||||
}]
|
||||
deliverImmediately:YES]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -37,14 +37,14 @@ objc_library(
|
||||
objc_library(
|
||||
name = "santactl_lib",
|
||||
srcs = [
|
||||
"main.m",
|
||||
"Commands/SNTCommandFileInfo.m",
|
||||
"Commands/SNTCommandRule.m",
|
||||
"Commands/SNTCommandStatus.m",
|
||||
"Commands/SNTCommandVersion.m",
|
||||
"Commands/SNTCommandMetrics.h",
|
||||
"Commands/SNTCommandMetrics.m",
|
||||
"Commands/SNTCommandRule.m",
|
||||
"Commands/SNTCommandStatus.m",
|
||||
"Commands/SNTCommandSync.m",
|
||||
"Commands/SNTCommandVersion.m",
|
||||
"main.m",
|
||||
] + select({
|
||||
"//:opt_build": [],
|
||||
"//conditions:default": [
|
||||
@@ -59,7 +59,6 @@ objc_library(
|
||||
":SNTCommandPrintLog",
|
||||
":santactl_cmd",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
@@ -91,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.
|
||||
|
||||
@@ -166,19 +166,10 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
__block NSDictionary *metrics;
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
|
||||
[[self.daemonConn remoteObjectProxy] metrics:^(NSDictionary *exportedMetrics) {
|
||||
[[self.daemonConn synchronousRemoteObjectProxy] metrics:^(NSDictionary *exportedMetrics) {
|
||||
metrics = exportedMetrics;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
// Wait a maximum of 5s for metrics collected from daemon to arrive.
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
|
||||
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
|
||||
}
|
||||
|
||||
metrics = [self filterMetrics:metrics withArguments:arguments];
|
||||
|
||||
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"],
|
||||
@@ -63,19 +96,20 @@ objc_library(
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
"//Source/common:SNTCommon",
|
||||
":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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -104,6 +138,9 @@ objc_library(
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:SantaVnodeHash",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -118,7 +155,6 @@ objc_library(
|
||||
":SNTDecisionCache",
|
||||
":SNTRuleTable",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -161,7 +197,6 @@ objc_library(
|
||||
hdrs = ["SNTPolicyProcessor.h"],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileInfo",
|
||||
@@ -174,6 +209,16 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "TTYWriter",
|
||||
srcs = ["TTYWriter.mm"],
|
||||
hdrs = ["TTYWriter.h"],
|
||||
deps = [
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:String",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTExecutionController",
|
||||
srcs = ["SNTExecutionController.mm"],
|
||||
@@ -186,9 +231,10 @@ objc_library(
|
||||
":SNTPolicyProcessor",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
":TTYWriter",
|
||||
"//Source/common:BranchPrediction",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
@@ -197,6 +243,8 @@ objc_library(
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:String",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
@@ -210,6 +258,7 @@ objc_library(
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -224,9 +273,11 @@ objc_library(
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":SNTEndpointSecurityClientBase",
|
||||
"//Source/common:SNTCommon",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:BranchPrediction",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SystemResources",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -246,7 +297,9 @@ objc_library(
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:String",
|
||||
"//Source/common:Unit",
|
||||
],
|
||||
)
|
||||
@@ -262,6 +315,7 @@ objc_library(
|
||||
":Metrics",
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:SNTLogging",
|
||||
],
|
||||
)
|
||||
@@ -281,10 +335,50 @@ objc_library(
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":SNTExecutionController",
|
||||
"//Source/common:BranchPrediction",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTLogging",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTEndpointSecurityFileAccessAuthorizer",
|
||||
srcs = ["EventProviders/SNTEndpointSecurityFileAccessAuthorizer.mm"],
|
||||
hdrs = ["EventProviders/SNTEndpointSecurityFileAccessAuthorizer.h"],
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityEnricher",
|
||||
":EndpointSecurityLogger",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":RateLimiter",
|
||||
":SNTDecisionCache",
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":TTYWriter",
|
||||
":WatchItemPolicy",
|
||||
":WatchItems",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTStrengthify",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:SantaVnodeHash",
|
||||
"//Source/common:String",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTEndpointSecurityDeviceManager",
|
||||
srcs = ["EventProviders/SNTEndpointSecurityDeviceManager.mm"],
|
||||
@@ -309,9 +403,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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -342,6 +450,8 @@ objc_library(
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
],
|
||||
)
|
||||
@@ -357,6 +467,9 @@ objc_library(
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:SantaVnodeHash",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -366,6 +479,7 @@ objc_library(
|
||||
hdrs = ["Logs/EndpointSecurity/Serializers/Empty.h"],
|
||||
deps = [
|
||||
":EndpointSecuritySerializer",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -378,6 +492,7 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
"//Source/common:String",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -392,7 +507,6 @@ objc_library(
|
||||
":EndpointSecuritySerializerUtilities",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
],
|
||||
@@ -411,6 +525,7 @@ objc_library(
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:String",
|
||||
"//Source/common:santa_cc_proto_library_wrapper",
|
||||
],
|
||||
)
|
||||
@@ -435,6 +550,7 @@ objc_library(
|
||||
hdrs = ["Logs/EndpointSecurity/Writers/File.h"],
|
||||
deps = [
|
||||
":EndpointSecurityWriter",
|
||||
"//Source/common:BranchPrediction",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -478,6 +594,7 @@ objc_library(
|
||||
":EndpointSecurityWriterNull",
|
||||
":EndpointSecurityWriterSpool",
|
||||
":EndpointSecurityWriterSyslog",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
@@ -493,6 +610,7 @@ objc_library(
|
||||
hdrs = ["EventProviders/EndpointSecurity/Message.h"],
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -508,6 +626,8 @@ objc_library(
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:Platform",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -524,6 +644,7 @@ objc_library(
|
||||
":SNTPolicyProcessor",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
":WatchItems",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -565,16 +686,21 @@ objc_library(
|
||||
":Metrics",
|
||||
":SNTCompilerController",
|
||||
":SNTDaemonControlController",
|
||||
":SNTDecisionCache",
|
||||
":SNTEndpointSecurityAuthorizer",
|
||||
":SNTEndpointSecurityDeviceManager",
|
||||
":SNTEndpointSecurityFileAccessAuthorizer",
|
||||
":SNTEndpointSecurityRecorder",
|
||||
":SNTEndpointSecurityTamperResistance",
|
||||
":SNTExecutionController",
|
||||
":SNTNotificationQueue",
|
||||
":SNTSyncdQueue",
|
||||
":TTYWriter",
|
||||
":WatchItems",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
"//Source/common:SNTKVOManager",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
@@ -596,11 +722,14 @@ objc_library(
|
||||
":Metrics",
|
||||
":SNTCompilerController",
|
||||
":SNTDatabaseController",
|
||||
":SNTDecisionCache",
|
||||
":SNTEventTable",
|
||||
":SNTExecutionController",
|
||||
":SNTNotificationQueue",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
":TTYWriter",
|
||||
":WatchItems",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -630,6 +759,7 @@ objc_library(
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SystemResources",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -649,7 +779,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",
|
||||
@@ -674,6 +804,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",
|
||||
],
|
||||
)
|
||||
@@ -710,6 +852,7 @@ santa_unit_test(
|
||||
"EndpointSecurity",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -728,7 +871,7 @@ santa_unit_test(
|
||||
data = [
|
||||
"//Source/santad/testdata:binaryrules_testdata",
|
||||
],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "11.0",
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
"EndpointSecurity",
|
||||
@@ -756,9 +899,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",
|
||||
@@ -855,13 +998,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"],
|
||||
@@ -985,7 +1138,6 @@ santa_unit_test(
|
||||
":SNTDecisionCache",
|
||||
":SNTRuleTable",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:TestUtils",
|
||||
@@ -1007,6 +1159,8 @@ santa_unit_test(
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTEndpointSecurityClient",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
@@ -1029,7 +1183,6 @@ santa_unit_test(
|
||||
":SNTExecutionController",
|
||||
":SNTRuleTable",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileInfo",
|
||||
@@ -1057,12 +1210,39 @@ santa_unit_test(
|
||||
":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"],
|
||||
@@ -1075,6 +1255,7 @@ santa_unit_test(
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTEndpointSecurityTamperResistance",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
@@ -1100,6 +1281,7 @@ santa_unit_test(
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityRecorder",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:TestUtils",
|
||||
"//Source/common:Unit",
|
||||
"@OCMock",
|
||||
@@ -1166,18 +1348,21 @@ test_suite(
|
||||
":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,10 @@
|
||||
bail = YES;
|
||||
return;
|
||||
}
|
||||
[db close];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:[db databasePath] error:NULL];
|
||||
[db open];
|
||||
[self closeDeleteReopenDatabase:db];
|
||||
} else if ([db userVersion] > [self currentSupportedVersion]) {
|
||||
LOGW(@"Database version newer than supported. Deleting.");
|
||||
[self closeDeleteReopenDatabase:db];
|
||||
}
|
||||
}];
|
||||
|
||||
@@ -58,11 +59,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 = 7;
|
||||
|
||||
// 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];
|
||||
@@ -203,12 +210,47 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
[db executeUpdate:@"ALTER TABLE 'rules' ADD 'timestamp' INTEGER"];
|
||||
newVersion = 3;
|
||||
}
|
||||
|
||||
if (version < 4) {
|
||||
// Rename `shasum` column to `identifier`.
|
||||
[db executeUpdate:@"ALTER TABLE 'rules' RENAME COLUMN 'shasum' TO 'identifier'"];
|
||||
newVersion = 4;
|
||||
}
|
||||
|
||||
if (version < 5) {
|
||||
// Migrate SNTRuleType enum values
|
||||
// Note: The reordering is intentional so that the type values are in order
|
||||
// of precedence.
|
||||
[db executeUpdate:@"UPDATE rules SET type = 1000 WHERE type = 1"];
|
||||
[db executeUpdate:@"UPDATE rules SET type = 3000 WHERE type = 2"];
|
||||
[db executeUpdate:@"UPDATE rules SET type = 4000 WHERE type = 3"];
|
||||
[db executeUpdate:@"UPDATE rules SET type = 2000 WHERE type = 4"];
|
||||
|
||||
newVersion = 5;
|
||||
}
|
||||
|
||||
if (version < 6) {
|
||||
// Force hash identifiers for Binary and Certificate rules to always be lowercase
|
||||
[db executeUpdate:@"UPDATE rules SET identifier = LOWER(identifier) WHERE type = ? OR type = ?",
|
||||
@(SNTRuleTypeBinary), @(SNTRuleTypeCertificate)];
|
||||
|
||||
// Force team ID identifiers for TeamID rules to always be uppercase
|
||||
[db executeUpdate:@"UPDATE rules SET identifier = UPPER(identifier) WHERE type = ?",
|
||||
@(SNTRuleTypeTeamID)];
|
||||
|
||||
// Note: Intentionally not attempting to migrate exsting SigningID rules to enforce
|
||||
// the TeamID component to be uppercase. Since this is a newer rule type, it is
|
||||
// assumed to be unnecessary and we'd rather not maintain the SQL to perform this
|
||||
// migration automatically.
|
||||
|
||||
newVersion = 6;
|
||||
}
|
||||
|
||||
if (version < 7) {
|
||||
[db executeUpdate:@"ALTER TABLE 'rules' ADD 'customurl' TEXT"];
|
||||
newVersion = 7;
|
||||
}
|
||||
|
||||
// Save signing info for launchd and santad. Used to ensure they are always allowed.
|
||||
self.santadCSInfo = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
self.launchdCSInfo = [[MOLCodesignChecker alloc] initWithPID:1];
|
||||
@@ -229,20 +271,20 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)binaryRuleCount {
|
||||
- (NSUInteger)ruleCountForRuleType:(SNTRuleType)ruleType {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=1"];
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=?", @(ruleType)];
|
||||
}];
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)binaryRuleCount {
|
||||
return [self ruleCountForRuleType:SNTRuleTypeBinary];
|
||||
}
|
||||
|
||||
- (NSUInteger)certificateRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=2"];
|
||||
}];
|
||||
return count;
|
||||
return [self ruleCountForRuleType:SNTRuleTypeCertificate];
|
||||
}
|
||||
|
||||
- (NSUInteger)compilerRuleCount {
|
||||
@@ -264,22 +306,25 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
}
|
||||
|
||||
- (NSUInteger)teamIDRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=3"];
|
||||
}];
|
||||
return count;
|
||||
return [self ruleCountForRuleType:SNTRuleTypeTeamID];
|
||||
}
|
||||
|
||||
- (NSUInteger)signingIDRuleCount {
|
||||
return [self ruleCountForRuleType:SNTRuleTypeSigningID];
|
||||
}
|
||||
|
||||
- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
|
||||
return [[SNTRule alloc] initWithIdentifier:[rs stringForColumn:@"identifier"]
|
||||
state:[rs intForColumn:@"state"]
|
||||
type:[rs intForColumn:@"type"]
|
||||
customMsg:[rs stringForColumn:@"custommsg"]
|
||||
timestamp:[rs intForColumn:@"timestamp"]];
|
||||
SNTRule *r = [[SNTRule alloc] initWithIdentifier:[rs stringForColumn:@"identifier"]
|
||||
state:[rs intForColumn:@"state"]
|
||||
type:[rs intForColumn:@"type"]
|
||||
customMsg:[rs stringForColumn:@"custommsg"]
|
||||
timestamp:[rs intForColumn:@"timestamp"]];
|
||||
r.customURL = [rs stringForColumn:@"customurl"];
|
||||
return r;
|
||||
}
|
||||
|
||||
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
|
||||
signingID:(NSString *)signingID
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID {
|
||||
__block SNTRule *rule;
|
||||
@@ -287,12 +332,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 +360,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 +375,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=1000) "
|
||||
@"OR (identifier=? AND type=2000) "
|
||||
@"OR (identifier=? AND type=3000) "
|
||||
@"OR (identifier=? AND type=4000) LIMIT 1",
|
||||
binarySHA256, signingID, certificateSHA256, teamID];
|
||||
if ([rs next]) {
|
||||
rule = [self ruleFromResultSet:rs];
|
||||
}
|
||||
@@ -372,10 +434,10 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
}
|
||||
} else {
|
||||
if (![db executeUpdate:@"INSERT OR REPLACE INTO rules "
|
||||
@"(identifier, state, type, custommsg, timestamp) "
|
||||
@"VALUES (?, ?, ?, ?, ?);",
|
||||
@"(identifier, state, type, custommsg, customurl, timestamp) "
|
||||
@"VALUES (?, ?, ?, ?, ?, ?);",
|
||||
rule.identifier, @(rule.state), @(rule.type), rule.customMsg,
|
||||
@(rule.timestamp)]) {
|
||||
rule.customURL, @(rule.timestamp)]) {
|
||||
[self fillError:error
|
||||
code:SNTRuleTableErrorInsertOrReplaceFailed
|
||||
message:[db lastErrorMessage]];
|
||||
|
||||
@@ -36,16 +36,29 @@
|
||||
|
||||
- (SNTRule *)_exampleTeamIDRule {
|
||||
SNTRule *r = [[SNTRule alloc] init];
|
||||
r.identifier = @"teamID";
|
||||
r.identifier = @"ABCDEFGHIJ";
|
||||
r.state = SNTRuleStateBlock;
|
||||
r.type = SNTRuleTypeTeamID;
|
||||
r.customMsg = @"A teamID rule";
|
||||
return r;
|
||||
}
|
||||
|
||||
- (SNTRule *)_exampleSigningIDRuleIsPlatform:(BOOL)isPlatformBinary {
|
||||
SNTRule *r = [[SNTRule alloc] init];
|
||||
if (isPlatformBinary) {
|
||||
r.identifier = @"platform:signingID";
|
||||
} else {
|
||||
r.identifier = @"ABCDEFGHIJ:signingID";
|
||||
}
|
||||
r.state = SNTRuleStateBlock;
|
||||
r.type = SNTRuleTypeSigningID;
|
||||
r.customMsg = @"A signingID 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:@"ABCDEFGHIJ"];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.identifier, @"teamID");
|
||||
XCTAssertEqualObjects(r.identifier, @"ABCDEFGHIJ");
|
||||
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:@"ABCDEFGHIJ:signingID"
|
||||
certificateSHA256:nil
|
||||
teamID:nil];
|
||||
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.identifier, @"ABCDEFGHIJ: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:@"ABCDEFGHIJ:signingID"
|
||||
certificateSHA256:@"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258"
|
||||
teamID:@"ABCDEFGHIJ"];
|
||||
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:@"ABCDEFGHIJ:signingID"
|
||||
certificateSHA256:@"unknowncert"
|
||||
teamID:@"ABCDEFGHIJ"];
|
||||
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:@"ABCDEFGHIJ"];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.identifier, @"b");
|
||||
XCTAssertEqualObjects(r.identifier,
|
||||
@"7ae80b9ab38af0c63a9a81765f434d9a7cd8f720eb6037ef303de39d779bc258");
|
||||
XCTAssertEqual(r.type, SNTRuleTypeCertificate, @"Implicit rule ordering failed");
|
||||
|
||||
r = [self.sut ruleForBinarySHA256:@"unknown"
|
||||
signingID:@"ABCDEFGHIJ:signingID"
|
||||
certificateSHA256:@"unknown"
|
||||
teamID:@"ABCDEFGHIJ"];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.identifier, @"ABCDEFGHIJ:signingID");
|
||||
XCTAssertEqual(r.type, SNTRuleTypeSigningID, @"Implicit rule ordering failed (SigningID)");
|
||||
|
||||
r = [self.sut ruleForBinarySHA256:@"unknown"
|
||||
signingID:@"unknown"
|
||||
certificateSHA256:@"unknown"
|
||||
teamID:@"ABCDEFGHIJ"];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.identifier, @"ABCDEFGHIJ");
|
||||
XCTAssertEqual(r.type, SNTRuleTypeTeamID, @"Implicit rule ordering failed (TeamID)");
|
||||
}
|
||||
|
||||
- (void)testBadDatabase {
|
||||
|
||||
123
Source/santad/DataLayer/WatchItemPolicy.h
Normal file
123
Source/santad/DataLayer/WatchItemPolicy.h
Normal file
@@ -0,0 +1,123 @@
|
||||
/// 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;
|
||||
static constexpr bool kWatchItemPolicyDefaultEnableSilentMode = false;
|
||||
static constexpr bool kWatchItemPolicyDefaultEnableSilentTTYMode = 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,
|
||||
bool esm = kWatchItemPolicyDefaultEnableSilentMode,
|
||||
bool estm = kWatchItemPolicyDefaultEnableSilentTTYMode,
|
||||
std::string_view cm = "", std::vector<Process> procs = {})
|
||||
: name(n),
|
||||
path(p),
|
||||
path_type(pt),
|
||||
allow_read_access(ara),
|
||||
audit_only(ao),
|
||||
invert_process_exceptions(ipe),
|
||||
silent(esm),
|
||||
silent_tty(estm),
|
||||
custom_message(cm.length() == 0 ? std::nullopt
|
||||
: std::make_optional<std::string>(cm)),
|
||||
processes(std::move(procs)) {}
|
||||
|
||||
bool operator==(const WatchItemPolicy &other) const {
|
||||
// Note: Custom message isn't currently considered for equality purposes
|
||||
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 &&
|
||||
silent == other.silent && silent_tty == other.silent_tty &&
|
||||
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;
|
||||
bool silent;
|
||||
bool silent_tty;
|
||||
std::optional<std::string> custom_message;
|
||||
std::vector<Process> processes;
|
||||
|
||||
// WIP - No current way to control via config
|
||||
std::string version = "temp_version";
|
||||
};
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
|
||||
#endif
|
||||
135
Source/santad/DataLayer/WatchItems.h
Normal file
135
Source/santad/DataLayer/WatchItems.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/// 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 kWatchItemConfigKeyOptionsEnableSilentMode;
|
||||
extern NSString *const kWatchItemConfigKeyOptionsEnableSilentTTYMode;
|
||||
extern NSString *const kWatchItemConfigKeyOptionsCustomMessage;
|
||||
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
|
||||
824
Source/santad/DataLayer/WatchItems.mm
Normal file
824
Source/santad/DataLayer/WatchItems.mm
Normal file
@@ -0,0 +1,824 @@
|
||||
/// 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 kWatchItemConfigKeyOptionsEnableSilentMode = @"EnableSilentMode";
|
||||
NSString *const kWatchItemConfigKeyOptionsEnableSilentTTYMode = @"EnableSilentTTYMode";
|
||||
NSString *const kWatchItemConfigKeyOptionsCustomMessage = @"BlockMessage";
|
||||
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;
|
||||
|
||||
// Semi-arbitrary max custom message length. The goal is to protect against
|
||||
// potential unbounded lengths, but no real reason this cannot be higher.
|
||||
static constexpr NSUInteger kWatchItemConfigOptionCustomMessageMaxLength = 2048;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static inline bool GetBoolValue(NSDictionary *options, NSString *key, bool default_value) {
|
||||
return options[key] ? [options[key] boolValue] : default_value;
|
||||
}
|
||||
|
||||
// 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/>
|
||||
/// <key>EnableSilentMode</key>
|
||||
/// <true/>
|
||||
/// <key>EnableSilentTTYMode</key>
|
||||
/// <true/>
|
||||
/// <key>BlockMessage</key>
|
||||
/// <string>...</string>
|
||||
/// </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) {
|
||||
NSArray<NSString *> *boolOptions = @[
|
||||
kWatchItemConfigKeyOptionsAllowReadAccess,
|
||||
kWatchItemConfigKeyOptionsAuditOnly,
|
||||
kWatchItemConfigKeyOptionsInvertProcessExceptions,
|
||||
kWatchItemConfigKeyOptionsEnableSilentMode,
|
||||
kWatchItemConfigKeyOptionsEnableSilentTTYMode,
|
||||
];
|
||||
|
||||
for (NSString *key in boolOptions) {
|
||||
if (!VerifyConfigKey(options, key, [NSNumber class], err)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!VerifyConfigKey(options, kWatchItemConfigKeyOptionsCustomMessage, [NSString class], err,
|
||||
false,
|
||||
LenRangeValidator(0, kWatchItemConfigOptionCustomMessageMaxLength))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool allow_read_access = GetBoolValue(options, kWatchItemConfigKeyOptionsAllowReadAccess,
|
||||
kWatchItemPolicyDefaultAllowReadAccess);
|
||||
bool audit_only =
|
||||
GetBoolValue(options, kWatchItemConfigKeyOptionsAuditOnly, kWatchItemPolicyDefaultAuditOnly);
|
||||
bool invert_process_exceptions =
|
||||
GetBoolValue(options, kWatchItemConfigKeyOptionsInvertProcessExceptions,
|
||||
kWatchItemPolicyDefaultInvertProcessExceptions);
|
||||
bool enable_silent_mode = GetBoolValue(options, kWatchItemConfigKeyOptionsEnableSilentMode,
|
||||
kWatchItemPolicyDefaultEnableSilentMode);
|
||||
bool enable_silent_tty_mode = GetBoolValue(options, kWatchItemConfigKeyOptionsEnableSilentTTYMode,
|
||||
kWatchItemPolicyDefaultEnableSilentTTYMode);
|
||||
|
||||
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, enable_silent_mode,
|
||||
enable_silent_tty_mode,
|
||||
NSStringToUTF8StringView(options[kWatchItemConfigKeyOptionsCustomMessage]),
|
||||
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
|
||||
914
Source/santad/DataLayer/WatchItemsTest.mm
Normal file
914
Source/santad/DataLayer/WatchItemsTest.mm
Normal file
@@ -0,0 +1,914 @@
|
||||
/// 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];
|
||||
}
|
||||
|
||||
@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
|
||||
{
|
||||
// Check bool option keys
|
||||
for (NSString *key in @[
|
||||
kWatchItemConfigKeyOptionsAllowReadAccess,
|
||||
kWatchItemConfigKeyOptionsAuditOnly,
|
||||
kWatchItemConfigKeyOptionsInvertProcessExceptions,
|
||||
kWatchItemConfigKeyOptionsEnableSilentMode,
|
||||
kWatchItemConfigKeyOptionsEnableSilentTTYMode,
|
||||
]) {
|
||||
// Parse bool option with invliad type
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(
|
||||
@"",
|
||||
@{kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{key : @""}},
|
||||
policies, &err));
|
||||
|
||||
// Parse bool option with valid type
|
||||
XCTAssertTrue(ParseConfigSingleWatchItem(
|
||||
@"",
|
||||
@{kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{key : @(0)}},
|
||||
policies, &err));
|
||||
}
|
||||
|
||||
// Check other option keys
|
||||
|
||||
// kWatchItemConfigKeyOptionsCustomMessage - Invalid type
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(
|
||||
@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsCustomMessage : @[]}
|
||||
},
|
||||
policies, &err));
|
||||
|
||||
// kWatchItemConfigKeyOptionsCustomMessage zero length
|
||||
XCTAssertTrue(ParseConfigSingleWatchItem(
|
||||
@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions : @{kWatchItemConfigKeyOptionsCustomMessage : @""}
|
||||
},
|
||||
policies, &err));
|
||||
|
||||
// kWatchItemConfigKeyOptionsCustomMessage valid "normal" length
|
||||
XCTAssertTrue(ParseConfigSingleWatchItem(
|
||||
@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions :
|
||||
@{kWatchItemConfigKeyOptionsCustomMessage : @"This is a custom message"}
|
||||
},
|
||||
policies, &err));
|
||||
|
||||
// kWatchItemConfigKeyOptionsCustomMessage Invalid "long" length
|
||||
XCTAssertFalse(ParseConfigSingleWatchItem(
|
||||
@"", @{
|
||||
kWatchItemConfigKeyPaths : @[ @"a" ],
|
||||
kWatchItemConfigKeyOptions :
|
||||
@{kWatchItemConfigKeyOptionsCustomMessage : RepeatedString(@"A", 4096)}
|
||||
},
|
||||
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),
|
||||
kWatchItemConfigKeyOptionsEnableSilentMode : @(YES),
|
||||
kWatchItemConfigKeyOptionsEnableSilentMode : @(NO),
|
||||
kWatchItemConfigKeyOptionsCustomMessage : @"",
|
||||
},
|
||||
kWatchItemConfigKeyProcesses : @[
|
||||
@{kWatchItemConfigKeyProcessesBinaryPath : @"pa"},
|
||||
@{kWatchItemConfigKeyProcessesBinaryPath : @"pb"}
|
||||
]
|
||||
},
|
||||
policies, &err));
|
||||
|
||||
XCTAssertEqual(policies.size(), 2);
|
||||
XCTAssertEqual(*policies[0].get(), WatchItemPolicy("rule", "a", kWatchItemPolicyDefaultPathType,
|
||||
true, false, true, true, false, "", procs));
|
||||
XCTAssertEqual(*policies[1].get(), WatchItemPolicy("rule", "b", WatchItemPathType::kPrefix, true,
|
||||
false, true, true, false, "", 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,71 @@
|
||||
#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", static_cast<int>(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 +101,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 +120,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 +169,8 @@ void AuthResultCache::FlushCache(FlushCacheMode mode) {
|
||||
shared_esapi->ClearCache(Client());
|
||||
});
|
||||
}
|
||||
|
||||
[flush_count_ incrementForFieldValues:@[ FlushCacheReasonToString(reason) ]];
|
||||
}
|
||||
|
||||
NSArray<NSNumber *> *AuthResultCache::CacheCounts() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user