mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
349 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737525b746 | ||
|
|
8199348091 | ||
|
|
9f41fbb124 | ||
|
|
ff0efe952b | ||
|
|
c711129ac9 | ||
|
|
a56f6c5447 | ||
|
|
fadc9b505b | ||
|
|
c7766d5993 | ||
|
|
341abf044b | ||
|
|
b1cf83a7e3 | ||
|
|
013b0b40af | ||
|
|
6093118ba1 | ||
|
|
6719d4c32a | ||
|
|
1ce4756771 | ||
|
|
9a7dcefb92 | ||
|
|
59382bc3ac | ||
|
|
0725fccc7f | ||
|
|
166c0420e5 | ||
|
|
f4ec2d51ab | ||
|
|
d54ec98bd5 | ||
|
|
bbeb653c77 | ||
|
|
52ffe5fc50 | ||
|
|
ffd77fef9d | ||
|
|
47648d2d5c | ||
|
|
208b4a6ebc | ||
|
|
7f86366672 | ||
|
|
9e7847740f | ||
|
|
348ff8c006 | ||
|
|
476cd21653 | ||
|
|
7bf11abca0 | ||
|
|
466546f548 | ||
|
|
73c18851f9 | ||
|
|
650f6fac97 | ||
|
|
9764f1bd69 | ||
|
|
688d560b62 | ||
|
|
b6af5ade60 | ||
|
|
08ce693096 | ||
|
|
85cfa641ce | ||
|
|
72ed5ee4f9 | ||
|
|
ecf7040b87 | ||
|
|
cedbc0da19 | ||
|
|
ef9348e6f5 | ||
|
|
b23b528082 | ||
|
|
587ac2ddc8 | ||
|
|
14729210d3 | ||
|
|
c3d29e3c4a | ||
|
|
4b0ad39413 | ||
|
|
e8b7fdff64 | ||
|
|
35d42d0134 | ||
|
|
a42dd6e120 | ||
|
|
53a2bbdd1e | ||
|
|
e417d8847f | ||
|
|
a23b67d5de | ||
|
|
7502bc247f | ||
|
|
cf4dab55e0 | ||
|
|
e43ad30d4e | ||
|
|
d8928ac320 | ||
|
|
ac1c9d8b05 | ||
|
|
9b184ed4fb | ||
|
|
67883c5200 | ||
|
|
8e1e155c23 | ||
|
|
fb6aa850b3 | ||
|
|
7f06b8c11a | ||
|
|
978b33e450 | ||
|
|
f00ad32edd | ||
|
|
7b0d2fdbb8 | ||
|
|
1672e52b7b | ||
|
|
6cca5ab27d | ||
|
|
7e4af5e337 | ||
|
|
5ea4431901 | ||
|
|
b53818f556 | ||
|
|
0f5e551345 | ||
|
|
51b0f7146d | ||
|
|
f5882b3146 | ||
|
|
59c146b4af | ||
|
|
aaa2b0e259 | ||
|
|
9c6fd0677f | ||
|
|
344a35aaf6 | ||
|
|
45e36fa501 | ||
|
|
d5a7c5f1fa | ||
|
|
22aca6b505 | ||
|
|
375f7bd9cc | ||
|
|
7d58665e87 | ||
|
|
3b2d02f38d | ||
|
|
57fc2b0253 | ||
|
|
262adfecbd | ||
|
|
1606657bb3 | ||
|
|
b379819cfa | ||
|
|
b9f6005411 | ||
|
|
e31aa5cf39 | ||
|
|
77d191ae26 | ||
|
|
160195a1d4 | ||
|
|
f2ce92650b | ||
|
|
e89cdbcf64 | ||
|
|
6a697e00ea | ||
|
|
74d8fe30d1 | ||
|
|
7513c75f88 | ||
|
|
9bee43130e | ||
|
|
7fa23d4b97 | ||
|
|
42eb0a3669 | ||
|
|
1ea26f0ac9 | ||
|
|
c35e9978d3 | ||
|
|
e4c0d56bb6 | ||
|
|
908b1bcabe | ||
|
|
64e81bedc6 | ||
|
|
5dfab22fa7 | ||
|
|
5248e2a7eb | ||
|
|
e8db89c57c | ||
|
|
70474aba3e | ||
|
|
f4ad76b974 | ||
|
|
3b7061ea62 | ||
|
|
280d93ee08 | ||
|
|
f73463117f | ||
|
|
f93e1a56a0 | ||
|
|
d5195b55d2 | ||
|
|
15e5874d43 | ||
|
|
5e6fa09f1c | ||
|
|
ce2777ae94 | ||
|
|
f8a20d35b4 | ||
|
|
2e69370524 | ||
|
|
f9b4e00e0c | ||
|
|
e2e83a099c | ||
|
|
2cbf15566a | ||
|
|
1596990c65 | ||
|
|
221664436f | ||
|
|
65c660298c | ||
|
|
2b5d55781c | ||
|
|
84e6d6ccff | ||
|
|
c16f90f5f9 | ||
|
|
d503eae4d9 | ||
|
|
818518bb38 | ||
|
|
f499654951 | ||
|
|
a5e8d77d06 | ||
|
|
edac42e8b8 | ||
|
|
ce5e3d0ee4 | ||
|
|
3e51ec6b8a | ||
|
|
ed227f43d4 | ||
|
|
056ed75bf1 | ||
|
|
8f5f8de245 | ||
|
|
7c58648c35 | ||
|
|
3f3751eb18 | ||
|
|
7aa2d69ce6 | ||
|
|
f9a937a6e4 | ||
|
|
d2cbddd3fb | ||
|
|
ea7e11fc22 | ||
|
|
7530b8f5c1 | ||
|
|
64bb34b2ca | ||
|
|
c5c6037085 | ||
|
|
275a8ed607 | ||
|
|
28dd6cbaed | ||
|
|
8c466b4408 | ||
|
|
373c676306 | ||
|
|
d214d510e5 | ||
|
|
6314fe04e3 | ||
|
|
11d9c29daa | ||
|
|
60238f0ed2 | ||
|
|
7aa731a76f | ||
|
|
5a383ebd9a | ||
|
|
913af692e8 | ||
|
|
4d6140d047 | ||
|
|
2edd2ddfd2 | ||
|
|
1515929752 | ||
|
|
fc2c7ffb71 | ||
|
|
98ee36850a | ||
|
|
6f4a48866c | ||
|
|
51ca19b238 | ||
|
|
b8d7ed0c07 | ||
|
|
ff6bf0701d | ||
|
|
3be45fd6c0 | ||
|
|
d2e5aec635 | ||
|
|
be1169ffcb | ||
|
|
181c3ae573 | ||
|
|
5f0755efbf | ||
|
|
f0165089a4 | ||
|
|
5c98ef6897 | ||
|
|
e2f8ca9569 | ||
|
|
2029e239ca | ||
|
|
cae3578b62 | ||
|
|
16a8c651d5 | ||
|
|
4fdc1e5e41 | ||
|
|
1cdd04f9eb | ||
|
|
4d0af8838f | ||
|
|
0400e29264 | ||
|
|
2c6da7158d | ||
|
|
b0ab761568 | ||
|
|
b02336613a | ||
|
|
bd86145679 | ||
|
|
6dfd5ba084 | ||
|
|
72e292d80e | ||
|
|
6588c2342b | ||
|
|
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
|
||||
|
||||
38
.bazelrc
38
.bazelrc
@@ -3,17 +3,45 @@ 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 --host_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
|
||||
|
||||
@@ -1 +1 @@
|
||||
5.3.0
|
||||
7.0.0
|
||||
|
||||
12
.github/workflows/check-markdown.yml
vendored
12
.github/workflows/check-markdown.yml
vendored
@@ -9,6 +9,12 @@ jobs:
|
||||
markdown-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
- run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"
|
||||
- name: "Checkout Santa"
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # ratchet:actions/checkout@v4
|
||||
- name: "Check for deadlinks"
|
||||
uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 # ratchet:lycheeverse/lychee-action@v1
|
||||
with:
|
||||
fail: true
|
||||
- name: "Check for trailing whitespace and newlines"
|
||||
if: '!cancelled()'
|
||||
run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"
|
||||
|
||||
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@@ -1,5 +1,4 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -11,45 +10,41 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- 'Source/**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Run linters
|
||||
run: ./Testing/lint.sh
|
||||
|
||||
build_userspace:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
os: [macos-12, macos-13, macos-14]
|
||||
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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet: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-12, macos-13, macos-14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Run All Tests
|
||||
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=adhoc --test_output=errors
|
||||
|
||||
test_coverage:
|
||||
runs-on: macos-11
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Generate test coverage
|
||||
run: sh ./generate_cov.sh
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
uses: coverallsapp/github-action@09b709cf6a16e30b0808ba050c7a6e8a5ef13f8d # ratchet:coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ./bazel-out/_coverage/_coverage_report.dat
|
||||
|
||||
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
|
||||
|
||||
67
.github/workflows/e2e.yml
vendored
Normal file
67
.github/workflows/e2e.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: E2E
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 4 * * *' # Every day at 4:00 UTC (not to interfere with fuzzing)
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_vm:
|
||||
runs-on: e2e-host
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Update VM
|
||||
env:
|
||||
GCS_KEY: ${{ secrets.GCS_SERVICE_ACCOUNT_KEY }}
|
||||
run: |
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp.json
|
||||
echo "${GCS_KEY}" > ${GOOGLE_APPLICATION_CREDENTIALS}
|
||||
function cleanup {
|
||||
rm /tmp/gcp.json
|
||||
}
|
||||
trap cleanup EXIT
|
||||
python3 Testing/integration/actions/update_vm.py macOS_14.bundle.tar.gz
|
||||
|
||||
start_vm:
|
||||
runs-on: e2e-host
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Start VM
|
||||
env:
|
||||
RUNNER_REG_TOKEN: ${{ secrets.RUNNER_REG_TOKEN }}
|
||||
run: python3 Testing/integration/actions/start_vm.py macOS_14.bundle.tar.gz
|
||||
|
||||
integration:
|
||||
runs-on: e2e-vm
|
||||
env:
|
||||
VM_PASSWORD: ${{ secrets.VM_PASSWORD }}
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Add homebrew to PATH
|
||||
run: echo "/opt/homebrew/bin/" >> $GITHUB_PATH
|
||||
- name: Install configuration profile
|
||||
run: bazel run //Testing/integration:install_profile -- Testing/integration/configs/default.mobileconfig
|
||||
- name: Build, install, and sync santa
|
||||
run: |
|
||||
bazel run :reload --define=SANTA_BUILD_TYPE=adhoc
|
||||
bazel run //Testing/integration:allow_sysex
|
||||
- name: Test config changes
|
||||
run: ./Testing/integration/test_config_changes.sh
|
||||
- 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 &
|
||||
sudo santactl sync --debug
|
||||
- name: Run integration test binaries
|
||||
run: |
|
||||
bazel test //Testing/integration:integration_tests
|
||||
sleep 3
|
||||
bazel run //Testing/integration:dismiss_santa_popup || true
|
||||
- name: Test sync server changes
|
||||
run: ./Testing/integration/test_sync_changes.sh
|
||||
- name: Test USB blocking
|
||||
run: ./Testing/integration/test_usb.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*
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,7 @@
|
||||
*.profraw
|
||||
*.provisionprofile
|
||||
bazel-*
|
||||
Pods
|
||||
MODULE.bazel.lock
|
||||
Santa.xcodeproj/*
|
||||
Santa.xcworkspace/*
|
||||
CoverageData/*
|
||||
|
||||
5
.pyink-config
Normal file
5
.pyink-config
Normal file
@@ -0,0 +1,5 @@
|
||||
[tool.pyink]
|
||||
pyink = true
|
||||
line-length = 80
|
||||
pyink-indentation = 2
|
||||
pyink-use-majority-quotes = true
|
||||
4
BUILD
4
BUILD
@@ -1,7 +1,9 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
|
||||
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
|
||||
@@ -55,11 +53,10 @@ readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Inf
|
||||
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
|
||||
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
|
||||
readonly APP_PKG_SCRIPTS="${SCRATCH}/pkg_scripts"
|
||||
readonly ENTITLEMENTS="${SCRATCH}/entitlements"
|
||||
|
||||
readonly SCRIPT_PATH="$(/usr/bin/dirname -- ${BASH_SOURCE[0]})"
|
||||
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}"
|
||||
|
||||
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
@@ -67,19 +64,9 @@ readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
EN="${ENTITLEMENTS}/${BN}.entitlements"
|
||||
|
||||
echo "extracting ${BN} entitlements"
|
||||
/usr/bin/codesign -d --entitlements "${EN}" "${ARTIFACT}"
|
||||
if [[ -s "${EN}" ]]; then
|
||||
EN="--entitlements ${EN}"
|
||||
else
|
||||
EN=""
|
||||
fi
|
||||
|
||||
echo "codesigning ${BN}"
|
||||
/usr/bin/codesign --sign "${SIGNING_IDENTITY}" --keychain "${SIGNING_KEYCHAIN}" \
|
||||
${EN} --timestamp --force --generate-entitlement-der \
|
||||
--preserve-metadata=entitlements --timestamp --force --generate-entitlement-der \
|
||||
--options library,kill,runtime "${ARTIFACT}"
|
||||
done
|
||||
|
||||
@@ -92,7 +79,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 +153,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 +165,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;
|
||||
;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@@ -58,7 +59,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy]
|
||||
databaseRuleAddRules:@[ newRule ]
|
||||
cleanSlate:NO
|
||||
ruleCleanup:SNTRuleCleanupNone
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
|
||||
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.
|
||||
|
||||
|
||||
56
MODULE.bazel
Normal file
56
MODULE.bazel
Normal file
@@ -0,0 +1,56 @@
|
||||
module(name = "santa")
|
||||
|
||||
bazel_dep(name = "apple_support", version = "1.15.1", repo_name = "build_bazel_apple_support")
|
||||
bazel_dep(name = "abseil-cpp", version = "20240116.2", repo_name = "com_google_absl")
|
||||
bazel_dep(name = "rules_python", version = "0.33.2")
|
||||
bazel_dep(name = "rules_cc", version = "0.0.9")
|
||||
bazel_dep(name = "rules_apple", version = "3.8.0", repo_name = "build_bazel_rules_apple")
|
||||
bazel_dep(name = "rules_swift", version = "2.0.0-rc1", repo_name = "build_bazel_rules_swift")
|
||||
bazel_dep(name = "rules_fuzzing", version = "0.5.2")
|
||||
bazel_dep(name = "protobuf", version = "27.2", repo_name = "com_google_protobuf")
|
||||
bazel_dep(name = "googletest", version = "1.14.0.bcr.1", repo_name = "com_google_googletest")
|
||||
|
||||
# MOLCertificate
|
||||
bazel_dep(name = "molcertificate", version = "2.1", repo_name = "MOLCertificate")
|
||||
git_override(
|
||||
module_name = "molcertificate",
|
||||
commit = "34f0ccf68a34a07cc636ada89057c529f90bec3a",
|
||||
remote = "https://github.com/google/macops-molcertificate.git",
|
||||
)
|
||||
|
||||
# MOLAuthenticatingURLSession
|
||||
bazel_dep(name = "molauthenticatingurlsession", version = "3.0", repo_name = "MOLAuthenticatingURLSession")
|
||||
git_override(
|
||||
module_name = "molauthenticatingurlsession",
|
||||
commit = "0a50a67f29d635a4012981714c1dedef9ac25fe6",
|
||||
remote = "https://github.com/google/macops-molauthenticatingurlsession.git",
|
||||
)
|
||||
|
||||
# MOLCodesignChecker
|
||||
bazel_dep(name = "molcodesignchecker", version = "3.0", repo_name = "MOLCodesignChecker")
|
||||
git_override(
|
||||
module_name = "molcodesignchecker",
|
||||
commit = "5060bcc8baa90bae3b0ca705d14850328bbbec53",
|
||||
remote = "https://github.com/google/macops-molcodesignchecker.git",
|
||||
)
|
||||
|
||||
# MOLXPCConnection
|
||||
bazel_dep(name = "molxpcconnection", version = "2.1", repo_name = "MOLXPCConnection")
|
||||
git_override(
|
||||
module_name = "molxpcconnection",
|
||||
commit = "da816dc49becac96d941ef6a5c4153ed39d1fe7c",
|
||||
remote = "https://github.com/russellhancox/macops-molxpcconnection.git",
|
||||
)
|
||||
|
||||
# FMDB
|
||||
non_module_deps = use_extension("//:non_module_deps.bzl", "non_module_deps")
|
||||
use_repo(non_module_deps, "FMDB")
|
||||
use_repo(non_module_deps, "OCMock")
|
||||
|
||||
# Hedron's Compile Commands Extractor
|
||||
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
|
||||
git_override(
|
||||
module_name = "hedron_compile_commands",
|
||||
commit = "0e990032f3c5a866e72615cf67e5ce22186dcb97",
|
||||
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
|
||||
)
|
||||
20
README.md
20
README.md
@@ -1,10 +1,16 @@
|
||||
# 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" />
|
||||
<img src="./docs/images/santa-sleigh-256.png" height="128" alt="Santa Icon" />
|
||||
</p>
|
||||
|
||||
Santa is a binary authorization system for macOS. It consists of a system
|
||||
Santa is a binary and file access authorization system for macOS. It consists of a system
|
||||
extension that monitors for executions, a daemon that makes execution decisions
|
||||
based on the contents of a local database, a GUI agent that notifies the user in
|
||||
case of a block decision and a command-line utility for managing the system and
|
||||
@@ -15,7 +21,7 @@ It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
# Docs
|
||||
|
||||
The Santa docs are stored in the
|
||||
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
|
||||
[Docs](https://github.com/google/santa/blob/main/docs) directory and are published
|
||||
at https://santa.dev.
|
||||
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
@@ -42,9 +48,7 @@ disclosure reporting.
|
||||
the events database. In LOCKDOWN mode, only listed binaries are allowed to
|
||||
run.
|
||||
|
||||
* Event logging: When the kext is loaded, all binary launches are logged. When
|
||||
in either mode, all unknown or denied binaries are stored in the database to
|
||||
enable later aggregation.
|
||||
* Event logging: When the system extension is loaded, all binary launches are logged. When in either mode, all unknown or denied binaries are stored in the database to enable later aggregation.
|
||||
|
||||
* Certificate-based rules, with override levels: Instead of relying on a
|
||||
binary's hash (or 'fingerprint'), executables can be allowed/blocked by their
|
||||
@@ -134,7 +138,7 @@ A tool like Santa doesn't really lend itself to screenshots, so here's a video
|
||||
instead.
|
||||
|
||||
|
||||
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
|
||||
<p align="center"> <img src="./docs/images/santa-block.gif" alt="Santa Block Video" /> </p>
|
||||
|
||||
# Contributing
|
||||
Patches to this project are very much welcome. Please see the
|
||||
|
||||
14
SECURITY.md
14
SECURITY.md
@@ -1,12 +1,14 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vulnerability, we would appreciate private disclosure
|
||||
so that we can work on a fix before disclosure. Any vulnerabilities reported to us will be
|
||||
If you believe you have found a security vulnerability, we would appreciate a private report
|
||||
so that we can work on and release a fix before public disclosure. Any vulnerabilities reported to us will be
|
||||
disclosed publicly either when a new version with fixes is released or 90 days has passed,
|
||||
whichever comes first.
|
||||
|
||||
To report vulnerabilities to us privately, please e-mail `santa-team@google.com`.
|
||||
If you want to encrypt your e-mail, you can use our GPG key `0x92AFE41DAB49BBB6`
|
||||
available on pool.sks-keyservers.net:
|
||||
To report vulnerabilities to us privately, either:
|
||||
|
||||
`gpg --keyserver pool.sks-keyservers.net --recv-key 0x92AFE41DAB49BBB6`
|
||||
1) Report the vulnerability [through GitHub](https://github.com/google/santa/security/advisories/new).
|
||||
|
||||
2) E-mail `santa-team@google.com`. If you want to encrypt your e-mail, you can use our GPG key `0x92AFE41DAB49BBB6` available on keyserver.ubuntu.com:
|
||||
|
||||
`gpg --keyserver keyserver.ubuntu.com --recv-key 0x92AFE41DAB49BBB6`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
@@ -11,6 +11,7 @@ proto_library(
|
||||
name = "santa_proto",
|
||||
srcs = ["santa.proto"],
|
||||
deps = [
|
||||
"//Source/santad/ProcessTree:process_tree_proto",
|
||||
"@com_google_protobuf//:any_proto",
|
||||
"@com_google_protobuf//:timestamp_proto",
|
||||
],
|
||||
@@ -31,19 +32,132 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SystemResources",
|
||||
srcs = ["SystemResources.mm"],
|
||||
hdrs = ["SystemResources.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeepCopy",
|
||||
srcs = ["SNTDeepCopy.m"],
|
||||
hdrs = ["SNTDeepCopy.h"],
|
||||
)
|
||||
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
# This target shouldn't be used directly.
|
||||
# Use a more specific scoped type instead.
|
||||
objc_library(
|
||||
name = "ScopedTypeRef",
|
||||
hdrs = ["ScopedTypeRef.h"],
|
||||
visibility = ["//Source/common:__pkg__"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "ScopedCFTypeRef",
|
||||
hdrs = ["ScopedCFTypeRef.h"],
|
||||
deps = [
|
||||
":ScopedTypeRef",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "ScopedCFTypeRefTest",
|
||||
srcs = ["ScopedCFTypeRefTest.mm"],
|
||||
sdk_frameworks = [
|
||||
"Security",
|
||||
],
|
||||
deps = [
|
||||
":ScopedCFTypeRef",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "ScopedIOObjectRef",
|
||||
hdrs = ["ScopedIOObjectRef.h"],
|
||||
sdk_frameworks = [
|
||||
"IOKit",
|
||||
],
|
||||
deps = [
|
||||
":ScopedTypeRef",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "ScopedIOObjectRefTest",
|
||||
srcs = ["ScopedIOObjectRefTest.mm"],
|
||||
sdk_frameworks = [
|
||||
"IOKit",
|
||||
],
|
||||
deps = [
|
||||
":ScopedIOObjectRef",
|
||||
"//Source/santad:EndpointSecuritySerializerUtilities",
|
||||
],
|
||||
)
|
||||
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "CertificateHelpers",
|
||||
srcs = ["CertificateHelpers.m"],
|
||||
hdrs = ["CertificateHelpers.h"],
|
||||
deps = [
|
||||
"@MOLCertificate",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SigningIDHelpers",
|
||||
srcs = ["SigningIDHelpers.m"],
|
||||
hdrs = ["SigningIDHelpers.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -52,6 +166,7 @@ objc_library(
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTFileAccessEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
@@ -65,7 +180,7 @@ objc_library(
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTDeviceEvent",
|
||||
":SNTFileAccessEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
@@ -74,11 +189,11 @@ objc_library(
|
||||
|
||||
objc_library(
|
||||
name = "SNTCachedDecision",
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
srcs = ["SNTCachedDecision.mm"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SantaVnode",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -86,11 +201,29 @@ 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 = [
|
||||
":CertificateHelpers",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
textual_hdrs = ["SNTCommonEnums.h"],
|
||||
@@ -100,6 +233,10 @@ objc_library(
|
||||
name = "SNTConfigurator",
|
||||
srcs = ["SNTConfigurator.m"],
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
module_name = "santa_common_SNTConfigurator",
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
@@ -137,20 +274,12 @@ objc_library(
|
||||
hdrs = ["SNTFileInfo.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
":SantaVnode",
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTCommon",
|
||||
hdrs = ["SNTCommon.h"],
|
||||
defines = [
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
@@ -176,6 +305,9 @@ objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSyncConstants",
|
||||
@@ -185,7 +317,17 @@ objc_library(
|
||||
santa_unit_test(
|
||||
name = "SNTRuleTest",
|
||||
srcs = ["SNTRuleTest.m"],
|
||||
deps = [":SNTRule"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTSyncConstants",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRuleIdentifiers",
|
||||
srcs = ["SNTRuleIdentifiers.m"],
|
||||
hdrs = ["SNTRuleIdentifiers.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -207,13 +349,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(
|
||||
@@ -247,6 +395,7 @@ objc_library(
|
||||
":SNTCommonEnums",
|
||||
":SNTConfigurator",
|
||||
":SNTRule",
|
||||
":SNTRuleIdentifiers",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCUnprivilegedControlInterface",
|
||||
"@MOLCodesignChecker",
|
||||
@@ -287,11 +436,12 @@ objc_library(
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTRuleIdentifiers",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
":SantaVnode",
|
||||
"@MOLCertificate",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -334,16 +484,46 @@ santa_unit_test(
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTBlockMessageTest",
|
||||
srcs = ["SNTBlockMessageTest.m"],
|
||||
sdk_frameworks = [
|
||||
"AppKit",
|
||||
],
|
||||
deps = [
|
||||
":SNTBlockMessage_SantaGUI",
|
||||
":SNTConfigurator",
|
||||
":SNTFileAccessEvent",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTConfiguratorTest",
|
||||
srcs = ["SNTConfiguratorTest.m"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTConfigurator",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":PrefixTreeTest",
|
||||
":SNTBlockMessageTest",
|
||||
":SNTCachedDecisionTest",
|
||||
":SNTConfiguratorTest",
|
||||
":SNTFileInfoTest",
|
||||
":SNTKVOManagerTest",
|
||||
":SNTMetricSetTest",
|
||||
":SNTRuleTest",
|
||||
":SantaCacheTest",
|
||||
":ScopedCFTypeRefTest",
|
||||
":ScopedIOObjectRefTest",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
@@ -357,6 +537,8 @@ objc_library(
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":Platform",
|
||||
":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
|
||||
43
Source/common/CertificateHelpers.h
Normal file
43
Source/common/CertificateHelpers.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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/**
|
||||
Return a string representing publisher info from the provided certs
|
||||
|
||||
@param certs A certificate chain
|
||||
@param teamID A team ID to be displayed for apps from the App Store
|
||||
|
||||
@return A string that tries to be more helpful to users by extracting
|
||||
appropriate information from the certificate chain.
|
||||
*/
|
||||
NSString *Publisher(NSArray<MOLCertificate *> *certs, NSString *teamID);
|
||||
|
||||
/**
|
||||
Return an array of the underlying SecCertificateRef's for the given array
|
||||
of MOLCertificates.
|
||||
|
||||
@param certs An array of MOLCertificates
|
||||
|
||||
@return An array of SecCertificateRefs. WARNING: If the refs need to be used
|
||||
for a long time be careful to properly CFRetain/CFRelease the returned items.
|
||||
*/
|
||||
NSArray<id> *CertificateChain(NSArray<MOLCertificate *> *certs);
|
||||
|
||||
__END_DECLS
|
||||
42
Source/common/CertificateHelpers.m
Normal file
42
Source/common/CertificateHelpers.m
Normal file
@@ -0,0 +1,42 @@
|
||||
/// 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/CertificateHelpers.h"
|
||||
|
||||
#include <Security/SecCertificate.h>
|
||||
|
||||
NSString *Publisher(NSArray<MOLCertificate *> *certs, NSString *teamID) {
|
||||
MOLCertificate *leafCert = [certs firstObject];
|
||||
|
||||
if ([leafCert.commonName isEqualToString:@"Apple Mac OS Application Signing"]) {
|
||||
return [NSString stringWithFormat:@"App Store (Team ID: %@)", teamID];
|
||||
} else if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
NSArray<id> *CertificateChain(NSArray<MOLCertificate *> *certs) {
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[certs count]];
|
||||
for (MOLCertificate *cert in certs) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
return certArray;
|
||||
}
|
||||
41
Source/common/Platform.h
Normal file
41
Source/common/Platform.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/// 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_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
|
||||
|
||||
#if defined(MAC_OS_VERSION_14_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
|
||||
#define HAVE_MACOS_14 1
|
||||
#else
|
||||
#define HAVE_MACOS_14 0
|
||||
#endif
|
||||
|
||||
#if defined(MAC_OS_VERSION_15_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_15_0
|
||||
#define HAVE_MACOS_15 1
|
||||
#else
|
||||
#define HAVE_MACOS_15 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -28,7 +28,7 @@
|
||||
#define DEBUG_LOG(format, ...) // NOP
|
||||
#endif
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
template <typename ValueT>
|
||||
class PrefixTree {
|
||||
@@ -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);
|
||||
}
|
||||
@@ -70,6 +74,11 @@ class PrefixTree {
|
||||
node_count_ = 0;
|
||||
}
|
||||
|
||||
uint32_t NodeCount() {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return node_count_;
|
||||
}
|
||||
|
||||
#if SANTA_PREFIX_TREE_DEBUG
|
||||
void Print() {
|
||||
char buf[max_depth_ + 1];
|
||||
@@ -78,11 +87,6 @@ class PrefixTree {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
PrintLocked(root_, buf, 0);
|
||||
}
|
||||
|
||||
uint32_t NodeCount() {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return node_count_;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
@@ -121,7 +125,10 @@ class PrefixTree {
|
||||
return false;
|
||||
}
|
||||
|
||||
cur_byte = (uint8_t) * ++p;
|
||||
// Disabling clang format due to local/remote version differences.
|
||||
// clang-format off
|
||||
cur_byte = (uint8_t)*++p;
|
||||
// clang-format on
|
||||
} while (*p);
|
||||
|
||||
node->node_type_ = node_type;
|
||||
@@ -293,6 +300,6 @@ class PrefixTree {
|
||||
absl::Mutex lock_;
|
||||
};
|
||||
|
||||
} // namespace santa::common
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#define SANTA_PREFIX_TREE_DEBUG 1
|
||||
#include "Source/common/PrefixTree.h"
|
||||
|
||||
using santa::common::PrefixTree;
|
||||
using santa::PrefixTree;
|
||||
|
||||
@interface PrefixTreeTest : XCTestCase
|
||||
@end
|
||||
@@ -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), ^{
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
#import "Source/common/SNTFileAccessEvent.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@interface SNTBlockMessage : NSObject
|
||||
@@ -38,11 +41,15 @@
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage;
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForFileAccessEvent:(SNTFileAccessEvent *)event
|
||||
customMessage:(NSString *)customMessage;
|
||||
|
||||
///
|
||||
/// 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;
|
||||
+ (NSURL *)eventDetailURLForFileAccessEvent:(SNTFileAccessEvent *)event customURL:(NSString *)url;
|
||||
|
||||
///
|
||||
/// Strip HTML from a string, replacing <br /> with newline.
|
||||
|
||||
@@ -15,10 +15,15 @@
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTFileAccessEvent.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
static id ValueOrNull(id value) {
|
||||
return value ?: [NSNull null];
|
||||
}
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message {
|
||||
@@ -51,7 +56,11 @@
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
NSDictionary *options = @{
|
||||
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
|
||||
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
|
||||
};
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData options:options documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
@@ -82,6 +91,18 @@
|
||||
return [SNTBlockMessage formatMessage:message];
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForFileAccessEvent:(SNTFileAccessEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *message = customMessage;
|
||||
if (!message.length) {
|
||||
message = [[SNTConfigurator configurator] fileAccessBlockMessage];
|
||||
if (!message.length) {
|
||||
message = @"Access to a file has been denied.";
|
||||
}
|
||||
}
|
||||
return [SNTBlockMessage formatMessage:message];
|
||||
}
|
||||
|
||||
+ (NSString *)stringFromHTML:(NSString *)html {
|
||||
NSError *error;
|
||||
NSXMLDocument *xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
|
||||
@@ -109,46 +130,113 @@
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
+ (NSString *)replaceFormatString:(NSString *)str
|
||||
withDict:(NSDictionary<NSString *, NSString *> *)replacements {
|
||||
__block NSString *formatStr = str;
|
||||
|
||||
[replacements enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
|
||||
if ((id)value != [NSNull null]) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:key withString:value];
|
||||
}
|
||||
}];
|
||||
|
||||
return formatStr;
|
||||
}
|
||||
|
||||
//
|
||||
// The following "format strings" will be replaced in the URL provided by
|
||||
// `+eventDetailURLForEvent:customURL:templateMapping:`.
|
||||
//
|
||||
// %file_identifier% - The SHA-256 of the binary being executed.
|
||||
// %bundle_or_file_identifier% - The hash of the bundle containing this file or the file itself,
|
||||
// if no bundle hash is present.
|
||||
// %file_bundle_id% - The bundle id of the binary, if any.
|
||||
// %team_id% - The Team ID if present in the signature information.
|
||||
// %signing_id% - The Signing ID if present in the signature information.
|
||||
// %cdhash% - If signed, the CDHash.
|
||||
// %username% - The executing user's name.
|
||||
// %machine_id% - The configured machine ID for this host.
|
||||
// %hostname% - The machine's FQDN.
|
||||
// %uuid% - The machine's UUID.
|
||||
// %serial% - The machine's serial number.
|
||||
//
|
||||
+ (NSDictionary *)eventDetailTemplateMappingForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
return @{
|
||||
@"%file_sha%" : ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
|
||||
@"%file_identifier%" : ValueOrNull(event.fileSHA256),
|
||||
@"%bundle_or_file_identifier%" :
|
||||
ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
|
||||
@"%username%" : ValueOrNull(event.executingUser),
|
||||
@"%file_bundle_id%" : ValueOrNull(event.fileBundleID),
|
||||
@"%team_id%" : ValueOrNull(event.teamID),
|
||||
@"%signing_id%" : ValueOrNull(event.signingID),
|
||||
@"%cdhash%" : ValueOrNull(event.cdhash),
|
||||
@"%machine_id%" : ValueOrNull(config.machineID),
|
||||
@"%hostname%" : ValueOrNull([SNTSystemInfo longHostname]),
|
||||
@"%uuid%" : ValueOrNull([SNTSystemInfo hardwareUUID]),
|
||||
@"%serial%" : ValueOrNull([SNTSystemInfo serialNumber]),
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Everything from `+eventDetailTemplateMappingForEvent:` with the following file access
|
||||
// specific templates.
|
||||
//
|
||||
// %rule_version% - The version of the rule that was violated.
|
||||
// %rule_name% - The name of the rule that was violated.
|
||||
// %accessed_path% - The path accessed by the binary.
|
||||
//
|
||||
+ (NSDictionary *)fileAccessEventDetailTemplateMappingForEvent:(SNTFileAccessEvent *)event {
|
||||
NSMutableDictionary *d = [self eventDetailTemplateMappingForEvent:event].mutableCopy;
|
||||
[d addEntriesFromDictionary:@{
|
||||
@"%rule_version%" : ValueOrNull(event.ruleVersion),
|
||||
@"%rule_name%" : ValueOrNull(event.ruleName),
|
||||
@"%accessed_path%" : ValueOrNull(event.accessedPath),
|
||||
}];
|
||||
return d;
|
||||
}
|
||||
|
||||
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
|
||||
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
|
||||
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
|
||||
// The "format strings" in `templateMapping` will be replaced in the URL, if they are present.
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event
|
||||
customURL:(NSString *)url
|
||||
templateMapping:(NSDictionary *)templateMapping {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *hostname = [SNTSystemInfo longHostname];
|
||||
NSString *uuid = [SNTSystemInfo hardwareUUID];
|
||||
NSString *serial = [SNTSystemInfo serialNumber];
|
||||
NSString *formatStr = config.eventDetailURL;
|
||||
if (!formatStr.length) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_identifier%"
|
||||
withString:event.fileSHA256];
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%bundle_or_file_identifier%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
withString:event.executingUser];
|
||||
}
|
||||
if (config.machineID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
if (hostname.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%hostname%" withString:hostname];
|
||||
}
|
||||
if (uuid.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%uuid%" withString:uuid];
|
||||
}
|
||||
if (serial.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%" withString:serial];
|
||||
NSString *formatStr = url;
|
||||
if (!formatStr.length) {
|
||||
formatStr = config.eventDetailURL;
|
||||
if (!formatStr.length) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
if ([formatStr isEqualToString:@"null"]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
formatStr = [SNTBlockMessage replaceFormatString:formatStr withDict:templateMapping];
|
||||
NSURL *u = [NSURL URLWithString:formatStr];
|
||||
if (!u) {
|
||||
LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
|
||||
return [self eventDetailURLForEvent:event
|
||||
customURL:url
|
||||
templateMapping:[self eventDetailTemplateMappingForEvent:event]];
|
||||
}
|
||||
|
||||
+ (NSURL *)eventDetailURLForFileAccessEvent:(SNTFileAccessEvent *)event customURL:(NSString *)url {
|
||||
return [self eventDetailURLForEvent:event
|
||||
customURL:url
|
||||
templateMapping:[self fileAccessEventDetailTemplateMappingForEvent:event]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
129
Source/common/SNTBlockMessageTest.m
Normal file
129
Source/common/SNTBlockMessageTest.m
Normal file
@@ -0,0 +1,129 @@
|
||||
/// 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 <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#include "Source/common/SNTFileAccessEvent.h"
|
||||
#include "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@interface SNTBlockMessageTest : XCTestCase
|
||||
@property id mockConfigurator;
|
||||
@property id mockSystemInfo;
|
||||
@end
|
||||
|
||||
@implementation SNTBlockMessageTest
|
||||
|
||||
- (void)setUp {
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
OCMStub([self.mockConfigurator machineID]).andReturn(@"my_mid");
|
||||
|
||||
self.mockSystemInfo = OCMClassMock([SNTSystemInfo class]);
|
||||
OCMStub([self.mockSystemInfo longHostname]).andReturn(@"my_hn");
|
||||
OCMStub([self.mockSystemInfo hardwareUUID]).andReturn(@"my_u");
|
||||
OCMStub([self.mockSystemInfo serialNumber]).andReturn(@"my_s");
|
||||
}
|
||||
|
||||
- (void)testFormatMessage {
|
||||
NSString *input = @"Testing with somé Ünicode çharacters";
|
||||
NSAttributedString *got = [SNTBlockMessage formatMessage:input];
|
||||
XCTAssertEqualObjects([got string], input);
|
||||
}
|
||||
|
||||
- (void)testEventDetailURLForEvent {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
|
||||
se.fileSHA256 = @"my_fi";
|
||||
se.executingUser = @"my_un";
|
||||
se.fileBundleID = @"s.n.t";
|
||||
se.cdhash = @"abc";
|
||||
se.teamID = @"SNT";
|
||||
se.signingID = @"SNT:s.n.t";
|
||||
|
||||
NSString *url = @"http://"
|
||||
@"localhost?fs=%file_sha%&fi=%file_identifier%&bfi=%bundle_or_file_identifier%&"
|
||||
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
|
||||
@"un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
|
||||
NSString *wantUrl = @"http://"
|
||||
@"localhost?fs=my_fi&fi=my_fi&bfi=my_fi&"
|
||||
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
|
||||
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
|
||||
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
|
||||
|
||||
// Set fileBundleHash and test again for newly expected values
|
||||
se.fileBundleHash = @"my_fbh";
|
||||
|
||||
wantUrl = @"http://"
|
||||
@"localhost?fs=my_fbh&fi=my_fi&bfi=my_fbh&"
|
||||
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
|
||||
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
|
||||
gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
|
||||
|
||||
XCTAssertEqualObjects(gotUrl.absoluteString, wantUrl);
|
||||
|
||||
XCTAssertNil([SNTBlockMessage eventDetailURLForEvent:se customURL:nil]);
|
||||
XCTAssertNil([SNTBlockMessage eventDetailURLForEvent:se customURL:@"null"]);
|
||||
}
|
||||
|
||||
- (void)testEventDetailURLForFileAccessEvent {
|
||||
SNTFileAccessEvent *fae = [[SNTFileAccessEvent alloc] init];
|
||||
|
||||
fae.ruleVersion = @"my_rv";
|
||||
fae.ruleName = @"my_rn";
|
||||
fae.fileSHA256 = @"my_fi";
|
||||
fae.fileBundleID = @"s.n.t";
|
||||
fae.cdhash = @"abc";
|
||||
fae.teamID = @"SNT";
|
||||
fae.signingID = @"SNT:s.n.t";
|
||||
fae.accessedPath = @"my_ap";
|
||||
fae.executingUser = @"my_un";
|
||||
|
||||
NSString *url =
|
||||
@"http://"
|
||||
@"localhost?rv=%rule_version%&rn=%rule_name%&fi=%file_identifier%&"
|
||||
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
|
||||
@"ap=%accessed_path%&un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
|
||||
NSString *wantUrl = @"http://"
|
||||
@"localhost?rv=my_rv&rn=my_rn&fi=my_fi&"
|
||||
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
|
||||
@"ap=my_ap&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
|
||||
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:url];
|
||||
|
||||
XCTAssertEqualObjects(gotUrl.absoluteString, wantUrl);
|
||||
|
||||
XCTAssertNil([SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:nil]);
|
||||
XCTAssertNil([SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:@"null"]);
|
||||
}
|
||||
|
||||
- (void)testEventDetailURLMissingDetails {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
|
||||
se.fileSHA256 = @"my_fi";
|
||||
|
||||
NSString *url = @"http://localhost?fi=%file_identifier%";
|
||||
NSString *wantUrl = @"http://localhost?fi=my_fi";
|
||||
|
||||
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
|
||||
|
||||
XCTAssertEqualObjects(gotUrl.absoluteString, wantUrl);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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;
|
||||
|
||||
@@ -25,10 +25,13 @@
|
||||
///
|
||||
@interface SNTCachedDecision : NSObject
|
||||
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile;
|
||||
- (instancetype)initWithVnode:(SantaVnode)vnode NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property santa_vnode_id_t vnodeId;
|
||||
@property SantaVnode vnodeId;
|
||||
@property SNTEventState decision;
|
||||
@property SNTClientMode decisionClientMode;
|
||||
@property NSString *decisionExtra;
|
||||
@property NSString *sha256;
|
||||
|
||||
@@ -36,10 +39,15 @@
|
||||
@property NSString *certCommonName;
|
||||
@property NSArray<MOLCertificate *> *certChain;
|
||||
@property NSString *teamID;
|
||||
@property NSString *signingID;
|
||||
@property NSString *cdhash;
|
||||
@property NSDictionary *entitlements;
|
||||
@property BOOL entitlementsFiltered;
|
||||
|
||||
@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");
|
||||
@@ -16,11 +17,18 @@
|
||||
|
||||
@implementation SNTCachedDecision
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithVnode:(SantaVnode){}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile {
|
||||
return [self initWithVnode:SantaVnode::VnodeForFile(esFile)];
|
||||
}
|
||||
|
||||
- (instancetype)initWithVnode:(SantaVnode)vnode {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_vnodeId.fsid = (uint64_t)esFile->stat.st_dev;
|
||||
_vnodeId.fileid = esFile->stat.st_ino;
|
||||
_vnodeId = vnode;
|
||||
}
|
||||
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,38 @@
|
||||
/// 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,
|
||||
|
||||
SNTRuleTypeCDHash = 500,
|
||||
SNTRuleTypeBinary = 1000,
|
||||
SNTRuleTypeSigningID = 2000,
|
||||
SNTRuleTypeCertificate = 3000,
|
||||
SNTRuleTypeTeamID = 4000,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
@@ -46,32 +72,36 @@ 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,
|
||||
SNTEventStateBlockCDHash = 1ULL << 23,
|
||||
|
||||
// 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,
|
||||
SNTEventStateAllowCDHash = 1ULL << 49,
|
||||
|
||||
// Block and Allow masks
|
||||
SNTEventStateBlock = 0xFF << 16,
|
||||
SNTEventStateAllow = 0xFF << 24
|
||||
SNTEventStateBlock = 0xFFFFFFULL << 16,
|
||||
SNTEventStateAllow = 0xFFFFFFULL << 40,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
@@ -94,6 +124,7 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeJSON,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
@@ -112,12 +143,69 @@ typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
|
||||
SNTSyncStatusTypeUnknown,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTSyncContentEncoding) {
|
||||
SNTSyncContentEncodingNone,
|
||||
SNTSyncContentEncodingDeflate,
|
||||
SNTSyncContentEncodingGzip,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
SNTMetricFormatTypeUnknown,
|
||||
SNTMetricFormatTypeRawJSON,
|
||||
SNTMetricFormatTypeMonarchJSON,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTOverrideFileAccessAction) {
|
||||
SNTOverrideFileAccessActionNone,
|
||||
SNTOverrideFileAccessActionAuditOnly,
|
||||
SNTOverrideFileAccessActionDiable,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTDeviceManagerStartupPreferences) {
|
||||
SNTDeviceManagerStartupPreferencesNone,
|
||||
SNTDeviceManagerStartupPreferencesUnmount,
|
||||
SNTDeviceManagerStartupPreferencesForceUnmount,
|
||||
SNTDeviceManagerStartupPreferencesRemount,
|
||||
SNTDeviceManagerStartupPreferencesForceRemount,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTSyncType) {
|
||||
SNTSyncTypeNormal,
|
||||
SNTSyncTypeClean,
|
||||
SNTSyncTypeCleanAll,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleCleanup) {
|
||||
SNTRuleCleanupNone,
|
||||
SNTRuleCleanupAll,
|
||||
SNTRuleCleanupNonTransitive,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
enum class FileAccessPolicyDecision {
|
||||
kNoPolicy,
|
||||
kDenied,
|
||||
kDeniedInvalidSignature,
|
||||
kAllowed,
|
||||
kAllowedReadAccess,
|
||||
kAllowedAuditOnly,
|
||||
};
|
||||
|
||||
enum class StatChangeStep {
|
||||
kNoChange = 0,
|
||||
kMessageCreate,
|
||||
kCodesignValidation,
|
||||
};
|
||||
|
||||
enum class StatResult {
|
||||
kOK = 0,
|
||||
kStatError,
|
||||
kDevnoInodeMismatch,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static const char *kSantaDPath =
|
||||
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
|
||||
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
///
|
||||
/// Enable Fail Close mode. Defaults to NO.
|
||||
/// This controls Santa's behavior when a failure occurs, such as an
|
||||
/// inability to read a file. By default, to prevent bugs or misconfiguration
|
||||
/// inability to read a file and as a default response when deadlines
|
||||
/// are about to expire. By default, to prevent bugs or misconfiguration
|
||||
/// from rendering a machine inoperable Santa will fail open and allow
|
||||
/// execution. With this setting enabled, Santa will fail closed if the client
|
||||
/// is in LOCKDOWN mode, offering a higher level of security but with a higher
|
||||
@@ -194,6 +195,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,9 +246,46 @@
|
||||
///
|
||||
@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;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when access to a file is blocked
|
||||
/// by a binary due to some rule in the current File Access policy if that rule
|
||||
/// doesn't provide a custom message. If this is not configured, a reasonable
|
||||
/// default is provided.
|
||||
///
|
||||
/// @note: This property is KVO compliant.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fileAccessBlockMessage;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
/// has been overridden, this is the host's UUID.
|
||||
/// Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
@@ -257,6 +302,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.
|
||||
@@ -294,6 +347,11 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventDetailText;
|
||||
|
||||
///
|
||||
/// This string represents the text to show on the "Dismiss" button in the UI instead of "Dismiss".
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *dismissText;
|
||||
|
||||
///
|
||||
/// In lockdown mode this is the message shown to the user when an unknown binary
|
||||
/// is blocked. If this message is not configured, a reasonable default is provided.
|
||||
@@ -340,14 +398,41 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *syncBaseURL;
|
||||
|
||||
///
|
||||
/// If enabled, syncing will use binary protobufs for transfer instead
|
||||
/// of JSON. Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL syncEnableProtoTransfer;
|
||||
|
||||
///
|
||||
/// Proxy settings for syncing.
|
||||
/// This dictionary is passed directly to NSURLSession. The allowed keys
|
||||
/// are loosely documented at
|
||||
/// https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants.
|
||||
/// https://developer.apple.com/documentation/cfnetwork/global-proxy-settings-constants.
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
@@ -364,9 +449,11 @@
|
||||
@property(nonatomic) NSDate *ruleSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// If YES a clean sync is required.
|
||||
/// Type of sync required (e.g. normal, clean, etc.).
|
||||
///
|
||||
@property(nonatomic) BOOL syncCleanRequired;
|
||||
@property(nonatomic) SNTSyncType syncTypeRequired;
|
||||
|
||||
#pragma mark - USB Settings
|
||||
|
||||
///
|
||||
/// USB Mount Blocking. Defaults to false.
|
||||
@@ -374,11 +461,44 @@
|
||||
@property(nonatomic) BOOL blockUSBMount;
|
||||
|
||||
///
|
||||
/// Comma-seperated `$ mount -o` arguments used for forced remounting of USB devices. Default
|
||||
/// Comma-separated `$ mount -o` arguments used for forced remounting of USB devices. Default
|
||||
/// to fully allow/deny without remounting if unset.
|
||||
///
|
||||
@property(nonatomic) NSArray<NSString *> *remountUSBMode;
|
||||
|
||||
///
|
||||
/// If set, defines the action that should be taken on existing USB mounts when
|
||||
/// Santa starts up.
|
||||
///
|
||||
/// Supported values are:
|
||||
/// * "Unmount": Unmount mass storage devices
|
||||
/// * "ForceUnmount": Force unmount mass storage devices
|
||||
///
|
||||
///
|
||||
/// Note: Existing mounts with mount flags that are a superset of RemountUSBMode
|
||||
/// are unaffected and left mounted.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTDeviceManagerStartupPreferences onStartUSBOptions;
|
||||
|
||||
///
|
||||
/// If set, will override the action taken when a file access rule violation
|
||||
/// occurs. This setting will apply across all rules in the file access policy.
|
||||
///
|
||||
/// Possible values are
|
||||
/// * "AuditOnly": When a rule is violated, it will be logged, but the access
|
||||
/// will not be blocked
|
||||
/// * "Disable": No access will be logged or blocked.
|
||||
///
|
||||
/// If not set, no override will take place and the file acces spolicy will
|
||||
/// apply as configured.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTOverrideFileAccessAction overrideFileAccessAction;
|
||||
|
||||
///
|
||||
/// Set the action that will override file access policy config action
|
||||
///
|
||||
- (void)setSyncServerOverrideFileAccessAction:(NSString *)action;
|
||||
|
||||
///
|
||||
/// If set, this over-rides the default machine ID used for syncing.
|
||||
///
|
||||
@@ -478,6 +598,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.
|
||||
///
|
||||
@@ -528,6 +654,24 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportTimeout;
|
||||
|
||||
///
|
||||
/// List of prefix strings for which individual entitlement keys with a matching
|
||||
/// prefix should not be logged.
|
||||
///
|
||||
@property(readonly, nonatomic) NSArray<NSString *> *entitlementsPrefixFilter;
|
||||
|
||||
///
|
||||
/// List of TeamIDs for which entitlements should not be logged. Use the string
|
||||
/// "platform" to refer to platform binaries.
|
||||
///
|
||||
@property(readonly, nonatomic) NSArray<NSString *> *entitlementsTeamIDFilter;
|
||||
|
||||
///
|
||||
/// List of enabled process annotations.
|
||||
/// This property is not KVO compliant.
|
||||
///
|
||||
@property(readonly, nonatomic) NSArray<NSString *> *enabledProcessAnnotations;
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
///
|
||||
|
||||
@@ -20,6 +20,21 @@
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
// Ensures the given object is an NSArray and only contains NSString value types
|
||||
static NSArray<NSString *> *EnsureArrayOfStrings(id obj) {
|
||||
if (![obj isKindOfClass:[NSArray class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
for (id item in obj) {
|
||||
if (![item isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@interface SNTConfigurator ()
|
||||
/// A NSUserDefaults object set to use the com.google.santa suite.
|
||||
@property(readonly, nonatomic) NSUserDefaults *defaults;
|
||||
@@ -38,6 +53,9 @@
|
||||
/// Holds the last processed hash of the static rules list.
|
||||
@property(atomic) NSDictionary *cachedStaticRules;
|
||||
|
||||
@property(readonly, nonatomic) NSString *syncStateFilePath;
|
||||
@property(nonatomic, copy) BOOL (^syncStateAccessAuthorizerBlock)();
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTConfigurator
|
||||
@@ -45,13 +63,19 @@
|
||||
/// 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";
|
||||
|
||||
/// The keys managed by a mobileconfig.
|
||||
static NSString *const kStaticRules = @"StaticRules";
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kSyncEnableProtoTransfer = @"SyncEnableProtoTransfer";
|
||||
static NSString *const kSyncProxyConfigKey = @"SyncProxyConfiguration";
|
||||
static NSString *const kSyncExtraHeadersKey = @"SyncExtraHeaders";
|
||||
static NSString *const kSyncEnableCleanSyncEventUpload = @"SyncEnableCleanSyncEventUpload";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
@@ -68,10 +92,12 @@ 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";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kDismissTextKey = @"DismissText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
|
||||
@@ -82,6 +108,8 @@ static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
|
||||
static NSString *const kEnableBadSignatureProtectionKey = @"EnableBadSignatureProtection";
|
||||
static NSString *const kFailClosedKey = @"FailClosed";
|
||||
static NSString *const kDisableUnknownEventUploadKey = @"DisableUnknownEventUpload";
|
||||
|
||||
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
|
||||
static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters";
|
||||
@@ -93,22 +121,38 @@ 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 kFileAccessBlockMessage = @"FileAccessBlockMessage";
|
||||
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";
|
||||
static NSString *const kFCMAPIKey = @"FCMAPIKey";
|
||||
|
||||
static NSString *const kEntitlementsPrefixFilterKey = @"EntitlementsPrefixFilter";
|
||||
static NSString *const kEntitlementsTeamIDFilterKey = @"EntitlementsTeamIDFilter";
|
||||
|
||||
static NSString *const kOnStartUSBOptions = @"OnStartUSBOptions";
|
||||
|
||||
static NSString *const kMetricFormat = @"MetricFormat";
|
||||
static NSString *const kMetricURL = @"MetricURL";
|
||||
static NSString *const kMetricExportInterval = @"MetricExportInterval";
|
||||
static NSString *const kMetricExportTimeout = @"MetricExportTimeout";
|
||||
static NSString *const kMetricExtraLabels = @"MetricExtraLabels";
|
||||
|
||||
static NSString *const kEnabledProcessAnnotations = @"EnabledProcessAnnotations";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kFailClosedKey = @"FailClosed";
|
||||
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
|
||||
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
|
||||
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
|
||||
@@ -118,21 +162,24 @@ static NSString *const kAllowedPathRegexKeyDeprecated = @"WhitelistRegex";
|
||||
static NSString *const kBlockedPathRegexKey = @"BlockedPathRegex";
|
||||
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";
|
||||
static NSString *const kMetricExportTimeout = @"MetricExportTimeout";
|
||||
static NSString *const kMetricExtraLabels = @"MetricExtraLabels";
|
||||
static NSString *const kOverrideFileAccessActionKey = @"OverrideFileAccessAction";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
static NSString *const kSyncCleanRequiredDeprecated = @"SyncCleanRequired";
|
||||
static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithSyncStateFile:kSyncStateFilePath
|
||||
syncStateAccessAuthorizer:^BOOL() {
|
||||
// Only access the sync state if a sync server is configured and running as root
|
||||
return self.syncBaseURL != nil && geteuid() == 0;
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath
|
||||
syncStateAccessAuthorizer:(BOOL (^)(void))syncStateAccessAuthorizer {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
Class number = [NSNumber class];
|
||||
@@ -154,8 +201,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBModeKey : array,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number,
|
||||
kSyncCleanRequiredDeprecated : number,
|
||||
kSyncTypeRequired : number,
|
||||
kEnableAllEventUploadKey : number,
|
||||
kOverrideFileAccessActionKey : string,
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
@@ -170,13 +219,16 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kBlockUSBMountKey : number,
|
||||
kRemountUSBModeKey : array,
|
||||
kOnStartUSBOptions : string,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kEnableSilentModeKey : number,
|
||||
kEnableSilentTTYModeKey : number,
|
||||
kAboutTextKey : string,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
kDismissTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kBannedUSBBlockMessage : string,
|
||||
@@ -185,11 +237,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncEnableProtoTransfer : number,
|
||||
kSyncEnableCleanSyncEventUpload : number,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kSyncExtraHeadersKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kClientContentEncoding : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
@@ -204,11 +260,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kSpoolDirectoryFileSizeThresholdKB : number,
|
||||
kSpoolDirectorySizeThresholdMB : number,
|
||||
kSpoolDirectoryEventMaxFlushTimeSec : number,
|
||||
kFileAccessPolicy : dictionary,
|
||||
kFileAccessPolicyPlist : string,
|
||||
kFileAccessBlockMessage : string,
|
||||
kFileAccessPolicyUpdateIntervalSec : number,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
kEnableDebugLogging : number,
|
||||
kEnableBackwardsCompatibleContentEncoding : number,
|
||||
kFCMProject : string,
|
||||
kFCMEntity : string,
|
||||
kFCMAPIKey : string,
|
||||
@@ -219,12 +278,26 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMetricExtraLabels : dictionary,
|
||||
kEnableAllEventUploadKey : number,
|
||||
kDisableUnknownEventUploadKey : number,
|
||||
kOverrideFileAccessActionKey : string,
|
||||
kEntitlementsPrefixFilterKey : array,
|
||||
kEntitlementsTeamIDFilterKey : array,
|
||||
kEnabledProcessAnnotations : array,
|
||||
};
|
||||
|
||||
_syncStateFilePath = syncStateFilePath;
|
||||
_syncStateAccessAuthorizerBlock = syncStateAccessAuthorizer;
|
||||
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
_configState = [self readForcedConfig];
|
||||
[self cacheStaticRules];
|
||||
|
||||
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
|
||||
if ([self migrateDeprecatedSyncStateKeys]) {
|
||||
// Save the updated sync state if any keys were migrated.
|
||||
[self saveSyncStateToDisk];
|
||||
}
|
||||
|
||||
_debugFlag = [[NSProcessInfo processInfo].arguments containsObject:@"--debug"];
|
||||
[self startWatchingDefaults];
|
||||
}
|
||||
@@ -233,7 +306,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, ^{
|
||||
@@ -292,13 +368,30 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
|
||||
return [self configStateSet];
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithObject:NSStringFromSelector(@selector(cachedStaticRules))];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncEnableProtoTransfer {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncExtraHeaders {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableCleanSyncEventUpload {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -323,6 +416,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingDismissText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -379,7 +476,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncCleanRequired {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncTypeRequired {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
@@ -407,6 +504,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicy {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyPlist {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyUpdateIntervalSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -435,10 +548,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -479,6 +588,18 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingOverrideFileAccessActionKey {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEntitlementsPrefixFilter {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEntitlementsTeamIDFilter {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
@@ -503,8 +624,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
|
||||
- (BOOL)failClosed {
|
||||
NSNumber *n = self.configState[kFailClosedKey];
|
||||
if (n) return [n boolValue];
|
||||
return NO;
|
||||
return [n boolValue] && self.clientMode == SNTClientModeLockdown;
|
||||
}
|
||||
|
||||
- (BOOL)enableTransitiveRules {
|
||||
@@ -589,6 +709,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return args;
|
||||
}
|
||||
|
||||
- (SNTDeviceManagerStartupPreferences)onStartUSBOptions {
|
||||
NSString *action = [self.configState[kOnStartUSBOptions] lowercaseString];
|
||||
|
||||
if ([action isEqualToString:@"unmount"]) {
|
||||
return SNTDeviceManagerStartupPreferencesUnmount;
|
||||
} else if ([action isEqualToString:@"forceunmount"]) {
|
||||
return SNTDeviceManagerStartupPreferencesForceUnmount;
|
||||
} else if ([action isEqualToString:@"remount"]) {
|
||||
return SNTDeviceManagerStartupPreferencesRemount;
|
||||
} else if ([action isEqualToString:@"forceremount"]) {
|
||||
return SNTDeviceManagerStartupPreferencesForceRemount;
|
||||
} else {
|
||||
return SNTDeviceManagerStartupPreferencesNone;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, SNTRule *> *)staticRules {
|
||||
return self.cachedStaticRules;
|
||||
}
|
||||
@@ -600,10 +736,19 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return url;
|
||||
}
|
||||
|
||||
- (BOOL)syncEnableProtoTransfer {
|
||||
NSNumber *number = self.configState[kSyncEnableProtoTransfer];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (NSDictionary *)syncProxyConfig {
|
||||
return self.configState[kSyncProxyConfigKey];
|
||||
}
|
||||
|
||||
- (NSDictionary *)syncExtraHeaders {
|
||||
return self.configState[kSyncExtraHeadersKey];
|
||||
}
|
||||
|
||||
- (BOOL)enablePageZeroProtection {
|
||||
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
|
||||
return number ? [number boolValue] : YES;
|
||||
@@ -619,6 +764,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];
|
||||
}
|
||||
@@ -635,6 +785,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return self.configState[kEventDetailTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)dismissText {
|
||||
return self.configState[kDismissTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)unknownBlockMessage {
|
||||
return self.configState[kUnknownBlockMessage];
|
||||
}
|
||||
@@ -682,6 +836,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];
|
||||
}
|
||||
@@ -707,12 +875,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
[self updateSyncStateForKey:kRuleSyncLastSuccess value:ruleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (BOOL)syncCleanRequired {
|
||||
return [self.syncState[kSyncCleanRequired] boolValue];
|
||||
- (SNTSyncType)syncTypeRequired {
|
||||
return (SNTSyncType)[self.syncState[kSyncTypeRequired] integerValue];
|
||||
}
|
||||
|
||||
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
|
||||
[self updateSyncStateForKey:kSyncCleanRequired value:@(syncCleanRequired)];
|
||||
- (void)setSyncTypeRequired:(SNTSyncType)syncTypeRequired {
|
||||
[self updateSyncStateForKey:kSyncTypeRequired value:@(syncTypeRequired)];
|
||||
}
|
||||
|
||||
- (NSString *)machineOwner {
|
||||
@@ -752,6 +920,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 +929,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 +959,29 @@ 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];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)fileAccessBlockMessage {
|
||||
return self.configState[kFileAccessBlockMessage];
|
||||
}
|
||||
|
||||
- (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 +1029,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];
|
||||
}
|
||||
@@ -864,6 +1056,34 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self.configState[kBlockUSBMountKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setSyncServerOverrideFileAccessAction:(NSString *)action {
|
||||
NSString *a = [action lowercaseString];
|
||||
if ([a isEqualToString:@"auditonly"] || [a isEqualToString:@"disable"] ||
|
||||
[a isEqualToString:@"none"] || [a isEqualToString:@""]) {
|
||||
[self updateSyncStateForKey:kOverrideFileAccessActionKey value:action];
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTOverrideFileAccessAction)overrideFileAccessAction {
|
||||
NSString *action = [self.syncState[kOverrideFileAccessActionKey] lowercaseString];
|
||||
|
||||
if (!action) {
|
||||
action = [self.configState[kOverrideFileAccessActionKey] lowercaseString];
|
||||
if (!action) {
|
||||
return SNTOverrideFileAccessActionNone;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: `auditonly` without an underscore is a deprecated, but still accepted form.
|
||||
if ([action isEqualToString:@"audit_only"] || [action isEqualToString:@"auditonly"]) {
|
||||
return SNTOverrideFileAccessActionAuditOnly;
|
||||
} else if ([action isEqualToString:@"disable"]) {
|
||||
return SNTOverrideFileAccessActionDiable;
|
||||
} else {
|
||||
return SNTOverrideFileAccessActionNone;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns YES if all of the necessary options are set to export metrics, NO
|
||||
/// otherwise.
|
||||
@@ -915,6 +1135,16 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return self.configState[kMetricExtraLabels];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)enabledProcessAnnotations {
|
||||
NSArray<NSString *> *annotations = self.configState[kEnabledProcessAnnotations];
|
||||
for (id annotation in annotations) {
|
||||
if (![annotation isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
///
|
||||
@@ -933,12 +1163,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
/// Read the saved syncState.
|
||||
///
|
||||
- (NSMutableDictionary *)readSyncStateFromDisk {
|
||||
// Only read the sync state if a sync server is configured.
|
||||
if (!self.syncBaseURL) return nil;
|
||||
// Only santad should read this file.
|
||||
if (geteuid() != 0) return nil;
|
||||
if (!self.syncStateAccessAuthorizerBlock()) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableDictionary *syncState =
|
||||
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
|
||||
[NSMutableDictionary dictionaryWithContentsOfFile:self.syncStateFilePath];
|
||||
for (NSString *key in syncState.allKeys) {
|
||||
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
|
||||
@@ -948,24 +1178,54 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return syncState;
|
||||
}
|
||||
|
||||
///
|
||||
/// Migrate any deprecated sync state keys/values to alternative keys/values.
|
||||
///
|
||||
/// Returns YES if any keys were migrated. Otherwise NO.
|
||||
///
|
||||
- (BOOL)migrateDeprecatedSyncStateKeys {
|
||||
// Currently only one key to migrate
|
||||
if (!self.syncState[kSyncCleanRequiredDeprecated]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
|
||||
// If the kSyncTypeRequired key exists, its current value will take precedence.
|
||||
// Otherwise, migrate the old value to be compatible with the new logic.
|
||||
if (!self.syncState[kSyncTypeRequired]) {
|
||||
syncState[kSyncTypeRequired] = [self.syncState[kSyncCleanRequiredDeprecated] boolValue]
|
||||
? @(SNTSyncTypeClean)
|
||||
: @(SNTSyncTypeNormal);
|
||||
}
|
||||
|
||||
// Delete the deprecated key
|
||||
syncState[kSyncCleanRequiredDeprecated] = nil;
|
||||
|
||||
self.syncState = syncState;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
///
|
||||
/// Saves the current effective syncState to disk.
|
||||
///
|
||||
- (void)saveSyncStateToDisk {
|
||||
// Only save the sync state if a sync server is configured.
|
||||
if (!self.syncBaseURL) return;
|
||||
// Only santad should write to this file.
|
||||
if (geteuid() != 0) return;
|
||||
if (!self.syncStateAccessAuthorizerBlock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Either remove
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
syncState[kAllowedPathRegexKey] = [syncState[kAllowedPathRegexKey] pattern];
|
||||
syncState[kBlockedPathRegexKey] = [syncState[kBlockedPathRegexKey] pattern];
|
||||
[syncState writeToFile:kSyncStateFilePath atomically:YES];
|
||||
[syncState writeToFile:self.syncStateFilePath atomically:YES];
|
||||
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0600}
|
||||
ofItemAtPath:kSyncStateFilePath
|
||||
ofItemAtPath:self.syncStateFilePath
|
||||
error:NULL];
|
||||
}
|
||||
|
||||
@@ -973,6 +1233,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
self.syncState = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
- (NSArray *)entitlementsPrefixFilter {
|
||||
return EnsureArrayOfStrings(self.configState[kEntitlementsPrefixFilterKey]);
|
||||
}
|
||||
|
||||
- (NSArray *)entitlementsTeamIDFilter {
|
||||
return EnsureArrayOfStrings(self.configState[kEntitlementsTeamIDFilterKey]);
|
||||
}
|
||||
|
||||
#pragma mark Private Defaults Methods
|
||||
|
||||
- (NSRegularExpression *)expressionForPattern:(NSString *)pattern {
|
||||
@@ -981,6 +1249,39 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
|
||||
}
|
||||
|
||||
- (void)applyOverrides:(NSMutableDictionary *)forcedConfig {
|
||||
// Overrides should only be applied under debug builds.
|
||||
#ifdef DEBUG
|
||||
if ([[[NSProcessInfo processInfo] processName] isEqualToString:@"xctest"] &&
|
||||
![[[NSProcessInfo processInfo] environment] objectForKey:@"ENABLE_CONFIG_OVERRIDES"]) {
|
||||
// By default, config overrides are not applied when running tests to help
|
||||
// mitigate potential issues due to unexpected config values. This behavior
|
||||
// can be overriden if desired by using the env variable: `ENABLE_CONFIG_OVERRIDES`.
|
||||
//
|
||||
// E.g.:
|
||||
// bazel test --test_env=ENABLE_CONFIG_OVERRIDES=1 ...other test args...
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *overrides = [NSDictionary dictionaryWithContentsOfFile:kConfigOverrideFilePath];
|
||||
for (NSString *key in overrides) {
|
||||
id obj = overrides[key];
|
||||
if (![obj isKindOfClass:self.forcedConfigKeyTypes[key]] ||
|
||||
([self.forcedConfigKeyTypes[key] isKindOfClass:[NSRegularExpression class]] &&
|
||||
![obj isKindOfClass:[NSString class]])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
forcedConfig[key] = obj;
|
||||
|
||||
if (self.forcedConfigKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [obj isKindOfClass:[NSString class]] ? obj : nil;
|
||||
forcedConfig[key] = [self expressionForPattern:pattern];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)readForcedConfig {
|
||||
NSMutableDictionary *forcedConfig = [NSMutableDictionary dictionary];
|
||||
for (NSString *key in self.forcedConfigKeyTypes) {
|
||||
@@ -992,6 +1293,9 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
forcedConfig[key] = [self expressionForPattern:pattern];
|
||||
}
|
||||
}
|
||||
|
||||
[self applyOverrides:forcedConfig];
|
||||
|
||||
return forcedConfig;
|
||||
}
|
||||
|
||||
@@ -1008,12 +1312,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 +1378,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];
|
||||
|
||||
102
Source/common/SNTConfiguratorTest.m
Normal file
102
Source/common/SNTConfiguratorTest.m
Normal file
@@ -0,0 +1,102 @@
|
||||
/// Copyright 2024 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
@interface SNTConfigurator (Testing)
|
||||
- (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath
|
||||
syncStateAccessAuthorizer:(BOOL (^)(void))syncStateAccessAuthorizer;
|
||||
|
||||
@property NSDictionary *syncState;
|
||||
@end
|
||||
|
||||
@interface SNTConfiguratorTest : XCTestCase
|
||||
@property NSFileManager *fileMgr;
|
||||
@property NSString *testDir;
|
||||
@end
|
||||
|
||||
@implementation SNTConfiguratorTest
|
||||
|
||||
- (void)setUp {
|
||||
self.fileMgr = [NSFileManager defaultManager];
|
||||
self.testDir =
|
||||
[NSString stringWithFormat:@"%@santa-configurator-%d", NSTemporaryDirectory(), getpid()];
|
||||
|
||||
XCTAssertTrue([self.fileMgr createDirectoryAtPath:self.testDir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil]);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
XCTAssertTrue([self.fileMgr removeItemAtPath:self.testDir error:nil]);
|
||||
}
|
||||
|
||||
- (void)runMigrationTestsWithSyncState:(NSDictionary *)syncStatePlist
|
||||
verifier:(void (^)(SNTConfigurator *))verifierBlock {
|
||||
NSString *syncStatePlistPath =
|
||||
[NSString stringWithFormat:@"%@/test-sync-state.plist", self.testDir];
|
||||
|
||||
XCTAssertTrue([syncStatePlist writeToFile:syncStatePlistPath atomically:YES]);
|
||||
|
||||
SNTConfigurator *cfg = [[SNTConfigurator alloc] initWithSyncStateFile:syncStatePlistPath
|
||||
syncStateAccessAuthorizer:^{
|
||||
// Allow all access to the test plist
|
||||
return YES;
|
||||
}];
|
||||
|
||||
NSLog(@"sync state: %@", cfg.syncState);
|
||||
|
||||
verifierBlock(cfg);
|
||||
|
||||
XCTAssertTrue([self.fileMgr removeItemAtPath:syncStatePlistPath error:nil]);
|
||||
}
|
||||
|
||||
- (void)testInitMigratesSyncStateKeys {
|
||||
// SyncCleanRequired = YES
|
||||
[self runMigrationTestsWithSyncState:@{@"SyncCleanRequired" : [NSNumber numberWithBool:YES]}
|
||||
verifier:^(SNTConfigurator *cfg) {
|
||||
XCTAssertEqual(cfg.syncState.count, 1);
|
||||
XCTAssertNil(cfg.syncState[@"SyncCleanRequired"]);
|
||||
XCTAssertNotNil(cfg.syncState[@"SyncTypeRequired"]);
|
||||
XCTAssertEqual([cfg.syncState[@"SyncTypeRequired"] integerValue],
|
||||
SNTSyncTypeClean);
|
||||
XCTAssertEqual(cfg.syncState.count, 1);
|
||||
}];
|
||||
|
||||
// SyncCleanRequired = NO
|
||||
[self runMigrationTestsWithSyncState:@{@"SyncCleanRequired" : [NSNumber numberWithBool:NO]}
|
||||
verifier:^(SNTConfigurator *cfg) {
|
||||
XCTAssertEqual(cfg.syncState.count, 1);
|
||||
XCTAssertNil(cfg.syncState[@"SyncCleanRequired"]);
|
||||
XCTAssertNotNil(cfg.syncState[@"SyncTypeRequired"]);
|
||||
XCTAssertEqual([cfg.syncState[@"SyncTypeRequired"] integerValue],
|
||||
SNTSyncTypeNormal);
|
||||
XCTAssertEqual(cfg.syncState.count, 1);
|
||||
}];
|
||||
|
||||
// Empty state
|
||||
[self runMigrationTestsWithSyncState:@{}
|
||||
verifier:^(SNTConfigurator *cfg) {
|
||||
XCTAssertEqual(cfg.syncState.count, 0);
|
||||
XCTAssertNil(cfg.syncState[@"SyncCleanRequired"]);
|
||||
XCTAssertNil(cfg.syncState[@"SyncTypeRequired"]);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
27
Source/common/SNTDeepCopy.h
Normal file
27
Source/common/SNTDeepCopy.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/// 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>
|
||||
|
||||
@interface NSArray (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSDictionary (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy;
|
||||
|
||||
@end
|
||||
53
Source/common/SNTDeepCopy.m
Normal file
53
Source/common/SNTDeepCopy.m
Normal file
@@ -0,0 +1,53 @@
|
||||
/// 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/SNTDeepCopy.h"
|
||||
|
||||
@implementation NSArray (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy {
|
||||
NSMutableArray<__kindof NSObject *> *deepCopy = [NSMutableArray arrayWithCapacity:self.count];
|
||||
for (id object in self) {
|
||||
if ([object respondsToSelector:@selector(sntDeepCopy)]) {
|
||||
[deepCopy addObject:[object sntDeepCopy]];
|
||||
} else if ([object respondsToSelector:@selector(copyWithZone:)]) {
|
||||
[deepCopy addObject:[object copy]];
|
||||
} else {
|
||||
[deepCopy addObject:object];
|
||||
}
|
||||
}
|
||||
return deepCopy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSDictionary (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy {
|
||||
NSMutableDictionary<__kindof NSObject *, __kindof NSObject *> *deepCopy =
|
||||
[NSMutableDictionary dictionary];
|
||||
for (id key in self) {
|
||||
id value = self[key];
|
||||
if ([value respondsToSelector:@selector(sntDeepCopy)]) {
|
||||
deepCopy[key] = [value sntDeepCopy];
|
||||
} else if ([value respondsToSelector:@selector(copyWithZone:)]) {
|
||||
deepCopy[key] = [value copy];
|
||||
} else {
|
||||
deepCopy[key] = value;
|
||||
}
|
||||
}
|
||||
return deepCopy;
|
||||
}
|
||||
|
||||
@end
|
||||
53
Source/common/SNTFileAccessEvent.h
Normal file
53
Source/common/SNTFileAccessEvent.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/// 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 "Source/common/SNTStoredEvent.h"
|
||||
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
///
|
||||
@interface SNTFileAccessEvent : SNTStoredEvent <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// The watched path that was accessed
|
||||
///
|
||||
@property NSString *accessedPath;
|
||||
|
||||
///
|
||||
/// The rule version and name that were violated
|
||||
///
|
||||
@property NSString *ruleVersion;
|
||||
@property NSString *ruleName;
|
||||
|
||||
///
|
||||
/// If the process is part of a bundle, the name of the application
|
||||
///
|
||||
@property NSString *application;
|
||||
|
||||
///
|
||||
/// A string representing the publisher based on the signingChain
|
||||
///
|
||||
@property(readonly) NSString *publisherInfo;
|
||||
|
||||
///
|
||||
/// Return an array of the underlying SecCertificateRef's of the signingChain
|
||||
///
|
||||
/// WARNING: If the refs need to be used for a long time be careful to properly
|
||||
/// CFRetain/CFRelease the returned items.
|
||||
///
|
||||
@property(readonly) NSArray *signingChainCertRefs;
|
||||
|
||||
@end
|
||||
82
Source/common/SNTFileAccessEvent.m
Normal file
82
Source/common/SNTFileAccessEvent.m
Normal file
@@ -0,0 +1,82 @@
|
||||
/// 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"
|
||||
|
||||
#import "Source/common/CertificateHelpers.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)
|
||||
|
||||
#define DECODEARRAY(o, c) \
|
||||
do { \
|
||||
_##o = [decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [c class], nil] \
|
||||
forKey:@(#o)]; \
|
||||
} while (0)
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[super encodeWithCoder:coder];
|
||||
ENCODE(accessedPath);
|
||||
ENCODE(ruleVersion);
|
||||
ENCODE(ruleName);
|
||||
ENCODE(application);
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super initWithCoder:decoder];
|
||||
if (self) {
|
||||
DECODE(accessedPath, NSString);
|
||||
DECODE(ruleVersion, NSString);
|
||||
DECODE(ruleName, NSString);
|
||||
DECODE(application, NSString);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString
|
||||
stringWithFormat:@"SNTFileAccessEvent: Accessed: %@, By: %@", self.accessedPath, self.filePath];
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
return Publisher(self.signingChain, self.teamID);
|
||||
}
|
||||
|
||||
- (NSArray *)signingChainCertRefs {
|
||||
return CertificateChain(self.signingChain);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,6 +15,8 @@
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SantaVnode.h"
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
|
||||
///
|
||||
@@ -220,6 +222,11 @@
|
||||
///
|
||||
- (NSUInteger)fileSize;
|
||||
|
||||
///
|
||||
/// @return The devno/ino pair of the file
|
||||
///
|
||||
- (SantaVnode)vnode;
|
||||
|
||||
///
|
||||
/// @return The underlying file handle.
|
||||
///
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
@property NSString *path;
|
||||
@property NSFileHandle *fileHandle;
|
||||
@property NSUInteger fileSize;
|
||||
@property SantaVnode vnode;
|
||||
@property NSString *fileOwnerHomeDir;
|
||||
@property NSString *sha256Storage;
|
||||
|
||||
@@ -110,6 +111,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
_fileSize = fileStat->st_size;
|
||||
_vnode = (SantaVnode){.fsid = fileStat->st_dev, .fileid = fileStat->st_ino};
|
||||
|
||||
if (_fileSize == 0) return nil;
|
||||
|
||||
@@ -421,8 +423,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
d = self.bundle.infoDictionary;
|
||||
if (d) {
|
||||
// `-[NSBundle infoDictionary]` is heavily cached, changes to the Info.plist are not realized.
|
||||
// Use `CFBundleCopyInfoDictionaryInDirectory` instead, which does not appear to cache.
|
||||
NSString *bundlePath = [self bundlePath];
|
||||
if (bundlePath.length) {
|
||||
d = CFBridgingRelease(CFBundleCopyInfoDictionaryInDirectory(
|
||||
(__bridge CFURLRef)[NSURL fileURLWithPath:bundlePath]));
|
||||
}
|
||||
if (d.count) {
|
||||
self.infoDict = d;
|
||||
return self.infoDict;
|
||||
}
|
||||
@@ -572,6 +580,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 +654,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"];
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// The callback type when KVO notifications are received for observed key paths.
|
||||
// The first parameter is the previous value, the second paramter is the new value.
|
||||
// The first parameter is the previous value, the second parameter is the new value.
|
||||
typedef void (^KVOCallback)(id oldValue, id newValue);
|
||||
|
||||
@interface SNTKVOManager : NSObject
|
||||
|
||||
@@ -605,10 +605,15 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
|
||||
/** Export current state of the SNTMetricSet as an NSDictionary. */
|
||||
- (NSDictionary *)export {
|
||||
NSDictionary *exported = nil;
|
||||
NSDictionary *exported;
|
||||
|
||||
NSArray *callbacks;
|
||||
@synchronized(self) {
|
||||
callbacks = [_callbacks mutableCopy];
|
||||
}
|
||||
|
||||
// Invoke callbacks to ensure metrics are up to date.
|
||||
for (void (^cb)(void) in _callbacks) {
|
||||
for (void (^cb)(void) in callbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
@@ -639,20 +644,9 @@ NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format) {
|
||||
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics) {
|
||||
NSMutableDictionary *mutableMetrics = [metrics mutableCopy];
|
||||
|
||||
id formatter;
|
||||
|
||||
if (@available(macOS 10.13, *)) {
|
||||
NSISO8601DateFormatter *isoFormatter = [[NSISO8601DateFormatter alloc] init];
|
||||
|
||||
isoFormatter.formatOptions =
|
||||
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
|
||||
formatter = isoFormatter;
|
||||
} else {
|
||||
NSDateFormatter *localFormatter = [[NSDateFormatter alloc] init];
|
||||
[localFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
|
||||
[localFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
||||
formatter = localFormatter;
|
||||
}
|
||||
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
|
||||
formatter.formatOptions =
|
||||
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
|
||||
|
||||
for (NSString *metricName in mutableMetrics[@"metrics"]) {
|
||||
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];
|
||||
|
||||
@@ -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.
|
||||
@@ -74,4 +79,9 @@
|
||||
///
|
||||
- (void)resetTimestamp;
|
||||
|
||||
///
|
||||
/// Returns a dictionary representation of the rule.
|
||||
///
|
||||
- (NSDictionary *)dictionaryRepresentation;
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,8 +13,16 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
#include <CommonCrypto/CommonCrypto.h>
|
||||
#include <Kernel/kern/cs_blobs.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 +36,87 @@
|
||||
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;
|
||||
}
|
||||
|
||||
case SNTRuleTypeCDHash: {
|
||||
identifier = [[identifier lowercaseString] stringByTrimmingCharactersInSet:nonHex];
|
||||
if (identifier.length != CS_CDHASH_LEN * 2) {
|
||||
return nil;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_identifier = identifier;
|
||||
_state = state;
|
||||
_type = type;
|
||||
@@ -55,52 +144,63 @@
|
||||
- (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 if ([ruleTypeString isEqual:kRuleTypeCDHash]) {
|
||||
type = SNTRuleTypeCDHash;
|
||||
} 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 +220,7 @@
|
||||
ENCODE(@(self.state), @"state");
|
||||
ENCODE(@(self.type), @"type");
|
||||
ENCODE(self.customMsg, @"custommsg");
|
||||
ENCODE(self.customURL, @"customurl");
|
||||
ENCODE(@(self.timestamp), @"timestamp");
|
||||
}
|
||||
|
||||
@@ -130,11 +231,49 @@
|
||||
_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;
|
||||
}
|
||||
|
||||
- (NSString *)ruleStateToPolicyString:(SNTRuleState)state {
|
||||
switch (state) {
|
||||
case SNTRuleStateAllow: return kRulePolicyAllowlist;
|
||||
case SNTRuleStateAllowCompiler: return kRulePolicyAllowlistCompiler;
|
||||
case SNTRuleStateBlock: return kRulePolicyBlocklist;
|
||||
case SNTRuleStateSilentBlock: return kRulePolicySilentBlocklist;
|
||||
case SNTRuleStateRemove: return kRulePolicyRemove;
|
||||
case SNTRuleStateAllowTransitive: return @"AllowTransitive";
|
||||
// This should never be hit. But is here for completion.
|
||||
default: return @"Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)ruleTypeToString:(SNTRuleType)ruleType {
|
||||
switch (ruleType) {
|
||||
case SNTRuleTypeBinary: return kRuleTypeBinary;
|
||||
case SNTRuleTypeCertificate: return kRuleTypeCertificate;
|
||||
case SNTRuleTypeTeamID: return kRuleTypeTeamID;
|
||||
case SNTRuleTypeSigningID: return kRuleTypeSigningID;
|
||||
// This should never be hit. If we have rule types of Unknown then there's a
|
||||
// coding error somewhere.
|
||||
default: return @"Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an NSDictionary representation of the rule. Primarily use for
|
||||
// exporting rules.
|
||||
- (NSDictionary *)dictionaryRepresentation {
|
||||
return @{
|
||||
kRuleIdentifier : self.identifier,
|
||||
kRulePolicy : [self ruleStateToPolicyString:self.state],
|
||||
kRuleType : [self ruleTypeToString:self.type],
|
||||
kRuleCustomMsg : self.customMsg ?: @"",
|
||||
kRuleCustomURL : self.customURL ?: @""
|
||||
};
|
||||
}
|
||||
|
||||
#undef DECODE
|
||||
#undef ENCODE
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
51
Source/common/SNTRuleIdentifiers.h
Normal file
51
Source/common/SNTRuleIdentifiers.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/// Copyright 2024 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
/**
|
||||
* This file declares two types that are mirrors of each other.
|
||||
*
|
||||
* The C struct serves as a way to group and pass valid rule identifiers around
|
||||
* in order to minimize interface changes needed when new rule types are added
|
||||
* and also alleviate the need to allocate a short lived object.
|
||||
*
|
||||
* The Objective C class is used for an XPC boundary to easily pass rule
|
||||
* identifiers between Santa components.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
struct RuleIdentifiers {
|
||||
NSString *cdhash;
|
||||
NSString *binarySHA256;
|
||||
NSString *signingID;
|
||||
NSString *certificateSHA256;
|
||||
NSString *teamID;
|
||||
};
|
||||
|
||||
@interface SNTRuleIdentifiers : NSObject <NSSecureCoding>
|
||||
@property(readonly) NSString *cdhash;
|
||||
@property(readonly) NSString *binarySHA256;
|
||||
@property(readonly) NSString *signingID;
|
||||
@property(readonly) NSString *certificateSHA256;
|
||||
@property(readonly) NSString *teamID;
|
||||
|
||||
/// Please use `initWithRuleIdentifiers:`
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithRuleIdentifiers:(struct RuleIdentifiers)identifiers
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (struct RuleIdentifiers)toStruct;
|
||||
|
||||
@end
|
||||
73
Source/common/SNTRuleIdentifiers.m
Normal file
73
Source/common/SNTRuleIdentifiers.m
Normal file
@@ -0,0 +1,73 @@
|
||||
/// Copyright 2024 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTRuleIdentifiers.h"
|
||||
|
||||
@implementation SNTRuleIdentifiers
|
||||
|
||||
- (instancetype)initWithRuleIdentifiers:(struct RuleIdentifiers)identifiers {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_cdhash = identifiers.cdhash;
|
||||
_binarySHA256 = identifiers.binarySHA256;
|
||||
_signingID = identifiers.signingID;
|
||||
_certificateSHA256 = identifiers.certificateSHA256;
|
||||
_teamID = identifiers.teamID;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (struct RuleIdentifiers)toStruct {
|
||||
return (struct RuleIdentifiers){.cdhash = self.cdhash,
|
||||
.binarySHA256 = self.binarySHA256,
|
||||
.signingID = self.signingID,
|
||||
.certificateSHA256 = self.certificateSHA256,
|
||||
.teamID = self.teamID};
|
||||
}
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
#define ENCODE(obj, key) \
|
||||
if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [self init];
|
||||
if (self) {
|
||||
_cdhash = DECODE(NSString, @"cdhash");
|
||||
_binarySHA256 = DECODE(NSString, @"binarySHA256");
|
||||
_signingID = DECODE(NSString, @"signingID");
|
||||
_certificateSHA256 = DECODE(NSString, @"certificateSHA256");
|
||||
_teamID = DECODE(NSString, @"teamID");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.cdhash, @"cdhash");
|
||||
ENCODE(self.binarySHA256, @"binarySHA256");
|
||||
ENCODE(self.signingID, @"signingID");
|
||||
ENCODE(self.certificateSHA256, @"certificateSHA256");
|
||||
ENCODE(self.teamID, @"teamID");
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
@@ -14,6 +14,9 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
@interface SNTRuleTest : XCTestCase
|
||||
@@ -25,66 +28,169 @@
|
||||
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",
|
||||
@"custom_url" : @"https://example.com",
|
||||
}];
|
||||
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");
|
||||
XCTAssertEqualObjects(sut.customURL, @"https://example.com");
|
||||
|
||||
// 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 +200,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",
|
||||
}];
|
||||
@@ -113,4 +226,63 @@
|
||||
XCTAssertNil(sut);
|
||||
}
|
||||
|
||||
- (void)testRuleDictionaryRepresentation {
|
||||
NSDictionary *expectedTeamID = @{
|
||||
@"identifier" : @"ABCDEFGHIJ",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"TEAMID",
|
||||
@"custom_msg" : @"A custom block message",
|
||||
@"custom_url" : @"https://example.com",
|
||||
};
|
||||
|
||||
SNTRule *sut = [[SNTRule alloc] initWithDictionary:expectedTeamID];
|
||||
NSDictionary *dict = [sut dictionaryRepresentation];
|
||||
XCTAssertEqualObjects(expectedTeamID, dict);
|
||||
|
||||
NSDictionary *expectedBinary = @{
|
||||
@"identifier" : @"84de9c61777ca36b13228e2446d53e966096e78db7a72c632b5c185b2ffe68a6",
|
||||
@"policy" : @"BLOCKLIST",
|
||||
@"rule_type" : @"BINARY",
|
||||
@"custom_msg" : @"",
|
||||
@"custom_url" : @"",
|
||||
};
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:expectedBinary];
|
||||
dict = [sut dictionaryRepresentation];
|
||||
|
||||
XCTAssertEqualObjects(expectedBinary, dict);
|
||||
}
|
||||
|
||||
- (void)testRuleStateToPolicyString {
|
||||
NSDictionary *expected = @{
|
||||
@"identifier" : @"84de9c61777ca36b13228e2446d53e966096e78db7a72c632b5c185b2ffe68a6",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"BINARY",
|
||||
@"custom_msg" : @"A custom block message",
|
||||
@"custom_url" : @"https://example.com",
|
||||
};
|
||||
|
||||
SNTRule *sut = [[SNTRule alloc] initWithDictionary:expected];
|
||||
sut.state = SNTRuleStateBlock;
|
||||
XCTAssertEqualObjects(kRulePolicyBlocklist, [sut dictionaryRepresentation][kRulePolicy]);
|
||||
sut.state = SNTRuleStateSilentBlock;
|
||||
XCTAssertEqualObjects(kRulePolicySilentBlocklist, [sut dictionaryRepresentation][kRulePolicy]);
|
||||
sut.state = SNTRuleStateAllow;
|
||||
XCTAssertEqualObjects(kRulePolicyAllowlist, [sut dictionaryRepresentation][kRulePolicy]);
|
||||
sut.state = SNTRuleStateAllowCompiler;
|
||||
XCTAssertEqualObjects(kRulePolicyAllowlistCompiler, [sut dictionaryRepresentation][kRulePolicy]);
|
||||
// Invalid states
|
||||
sut.state = SNTRuleStateRemove;
|
||||
XCTAssertEqualObjects(kRulePolicyRemove, [sut dictionaryRepresentation][kRulePolicy]);
|
||||
}
|
||||
|
||||
/*
|
||||
- (void)testRuleTypeToString {
|
||||
SNTRule *sut = [[SNTRule alloc] init];
|
||||
XCTAssertEqual(kRuleTypeBinary, [sut ruleTypeToString:@""]);//SNTRuleTypeBinary]);
|
||||
XCTAssertEqual(kRuleTypeCertificate,[sut ruleTypeToString:SNTRuleTypeCertificate]);
|
||||
XCTAssertEqual(kRuleTypeTeamID, [sut ruleTypeToString:SNTRuleTypeTeamID]);
|
||||
XCTAssertEqual(kRuleTypeSigningID,[sut ruleTypeToString:SNTRuleTypeSigningID]);
|
||||
}*/
|
||||
|
||||
@end
|
||||
|
||||
@@ -100,6 +100,16 @@
|
||||
///
|
||||
@property NSString *teamID;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Signing ID if present in the signature information.
|
||||
///
|
||||
@property NSString *signingID;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the CDHash of the binary.
|
||||
///
|
||||
@property NSString *cdhash;
|
||||
|
||||
///
|
||||
/// The user who executed the binary.
|
||||
///
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
|
||||
ENCODE(self.signingChain, @"signingChain");
|
||||
ENCODE(self.teamID, @"teamID");
|
||||
ENCODE(self.signingID, @"signingID");
|
||||
ENCODE(self.cdhash, @"cdhash");
|
||||
|
||||
ENCODE(self.executingUser, @"executingUser");
|
||||
ENCODE(self.occurrenceDate, @"occurrenceDate");
|
||||
@@ -95,10 +97,12 @@
|
||||
|
||||
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
|
||||
_teamID = DECODE(NSString, @"teamID");
|
||||
_signingID = DECODE(NSString, @"signingID");
|
||||
_cdhash = DECODE(NSString, @"cdhash");
|
||||
|
||||
_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;
|
||||
@@ -31,7 +32,8 @@ extern NSString *const kClientModeMonitor;
|
||||
extern NSString *const kClientModeLockdown;
|
||||
extern NSString *const kBlockUSBMount;
|
||||
extern NSString *const kRemountUSBMode;
|
||||
extern NSString *const kCleanSync;
|
||||
extern NSString *const kCleanSyncDeprecated;
|
||||
extern NSString *const kSyncType;
|
||||
extern NSString *const kAllowedPathRegex;
|
||||
extern NSString *const kAllowedPathRegexDeprecated;
|
||||
extern NSString *const kBlockedPathRegex;
|
||||
@@ -41,6 +43,8 @@ extern NSString *const kCertificateRuleCount;
|
||||
extern NSString *const kCompilerRuleCount;
|
||||
extern NSString *const kTransitiveRuleCount;
|
||||
extern NSString *const kTeamIDRuleCount;
|
||||
extern NSString *const kSigningIDRuleCount;
|
||||
extern NSString *const kCDHashRuleCount;
|
||||
extern NSString *const kFullSyncInterval;
|
||||
extern NSString *const kFCMToken;
|
||||
extern NSString *const kFCMFullSyncInterval;
|
||||
@@ -52,6 +56,7 @@ extern NSString *const kEnableTransitiveRulesDeprecated;
|
||||
extern NSString *const kEnableTransitiveRulesSuperDeprecated;
|
||||
extern NSString *const kEnableAllEventUpload;
|
||||
extern NSString *const kDisableUnknownEventUpload;
|
||||
extern NSString *const kOverrideFileAccessAction;
|
||||
|
||||
extern NSString *const kEvents;
|
||||
extern NSString *const kFileSHA256;
|
||||
@@ -65,11 +70,15 @@ extern NSString *const kDecisionAllowBinary;
|
||||
extern NSString *const kDecisionAllowCertificate;
|
||||
extern NSString *const kDecisionAllowScope;
|
||||
extern NSString *const kDecisionAllowTeamID;
|
||||
extern NSString *const kDecisionAllowSigningID;
|
||||
extern NSString *const kDecisionAllowCDHash;
|
||||
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 kDecisionBlockCDHash;
|
||||
extern NSString *const kDecisionUnknown;
|
||||
extern NSString *const kDecisionBundleBinary;
|
||||
extern NSString *const kLoggedInUsers;
|
||||
@@ -94,6 +103,8 @@ extern NSString *const kCertOU;
|
||||
extern NSString *const kCertValidFrom;
|
||||
extern NSString *const kCertValidUntil;
|
||||
extern NSString *const kTeamID;
|
||||
extern NSString *const kSigningID;
|
||||
extern NSString *const kCDHash;
|
||||
extern NSString *const kQuarantineDataURL;
|
||||
extern NSString *const kQuarantineRefererURL;
|
||||
extern NSString *const kQuarantineTimestamp;
|
||||
@@ -117,7 +128,10 @@ extern NSString *const kRuleType;
|
||||
extern NSString *const kRuleTypeBinary;
|
||||
extern NSString *const kRuleTypeCertificate;
|
||||
extern NSString *const kRuleTypeTeamID;
|
||||
extern NSString *const kRuleTypeSigningID;
|
||||
extern NSString *const kRuleTypeCDHash;
|
||||
extern NSString *const kRuleCustomMsg;
|
||||
extern NSString *const kRuleCustomURL;
|
||||
extern NSString *const kCursor;
|
||||
|
||||
extern NSString *const kBackoffInterval;
|
||||
@@ -129,6 +143,9 @@ extern NSString *const kLogSync;
|
||||
|
||||
extern const NSUInteger kDefaultEventBatchSize;
|
||||
|
||||
extern NSString *const kPostflightRulesReceived;
|
||||
extern NSString *const kPostflightRulesProcessed;
|
||||
|
||||
///
|
||||
/// kDefaultFullSyncInterval
|
||||
/// kDefaultFCMFullSyncInterval
|
||||
|
||||
@@ -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";
|
||||
@@ -31,7 +32,8 @@ NSString *const kBlockUSBMount = @"block_usb_mount";
|
||||
NSString *const kRemountUSBMode = @"remount_usb_mode";
|
||||
NSString *const kClientModeMonitor = @"MONITOR";
|
||||
NSString *const kClientModeLockdown = @"LOCKDOWN";
|
||||
NSString *const kCleanSync = @"clean_sync";
|
||||
NSString *const kCleanSyncDeprecated = @"clean_sync";
|
||||
NSString *const kSyncType = @"sync_type";
|
||||
NSString *const kAllowedPathRegex = @"allowed_path_regex";
|
||||
NSString *const kAllowedPathRegexDeprecated = @"whitelist_regex";
|
||||
NSString *const kBlockedPathRegex = @"blocked_path_regex";
|
||||
@@ -41,10 +43,13 @@ 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 kCDHashRuleCount = @"cdhash_rule_count";
|
||||
NSString *const kFullSyncInterval = @"full_sync_interval";
|
||||
NSString *const kFCMToken = @"fcm_token";
|
||||
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
|
||||
NSString *const kFCMGlobalRuleSyncDeadline = @"fcm_global_rule_sync_deadline";
|
||||
NSString *const kOverrideFileAccessAction = @"override_file_access_action";
|
||||
|
||||
NSString *const kEnableBundles = @"enable_bundles";
|
||||
NSString *const kEnableBundlesDeprecated = @"bundles_enabled";
|
||||
@@ -66,11 +71,15 @@ 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 kDecisionAllowCDHash = @"ALLOW_CDHASH";
|
||||
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 kDecisionBlockCDHash = @"BLOCK_CDHASH";
|
||||
NSString *const kDecisionUnknown = @"UNKNOWN";
|
||||
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
|
||||
NSString *const kLoggedInUsers = @"logged_in_users";
|
||||
@@ -95,6 +104,8 @@ 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 kCDHash = @"cdhash";
|
||||
NSString *const kQuarantineDataURL = @"quarantine_data_url";
|
||||
NSString *const kQuarantineRefererURL = @"quarantine_referer_url";
|
||||
NSString *const kQuarantineTimestamp = @"quarantine_timestamp";
|
||||
@@ -118,7 +129,10 @@ NSString *const kRuleType = @"rule_type";
|
||||
NSString *const kRuleTypeBinary = @"BINARY";
|
||||
NSString *const kRuleTypeCertificate = @"CERTIFICATE";
|
||||
NSString *const kRuleTypeTeamID = @"TEAMID";
|
||||
NSString *const kRuleTypeSigningID = @"SIGNINGID";
|
||||
NSString *const kRuleTypeCDHash = @"CDHASH";
|
||||
NSString *const kRuleCustomMsg = @"custom_msg";
|
||||
NSString *const kRuleCustomURL = @"custom_url";
|
||||
NSString *const kCursor = @"cursor";
|
||||
|
||||
NSString *const kBackoffInterval = @"backoff";
|
||||
@@ -128,6 +142,9 @@ NSString *const kRuleSync = @"rule_sync";
|
||||
NSString *const kConfigSync = @"config_sync";
|
||||
NSString *const kLogSync = @"log_sync";
|
||||
|
||||
NSString *const kPostflightRulesReceived = @"rules_received";
|
||||
NSString *const kPostflightRulesProcessed = @"rules_processed";
|
||||
|
||||
const NSUInteger kDefaultEventBatchSize = 50;
|
||||
const NSUInteger kDefaultFullSyncInterval = 600;
|
||||
const NSUInteger kDefaultPushNotificationsFullSyncInterval = 14400;
|
||||
|
||||
@@ -54,4 +54,19 @@
|
||||
///
|
||||
+ (NSString *)modelIdentifier;
|
||||
|
||||
///
|
||||
/// @return The Santa product version, e.g. 2024.6
|
||||
///
|
||||
+ (NSString *)santaProductVersion;
|
||||
|
||||
///
|
||||
/// @return The Santa build version, e.g. 655965194
|
||||
///
|
||||
+ (NSString *)santaBuildVersion;
|
||||
|
||||
///
|
||||
/// @return The full Santa versoin, e.g. 2024.6.655965194
|
||||
///
|
||||
+ (NSString *)santaFullVersion;
|
||||
|
||||
@end
|
||||
|
||||
@@ -74,6 +74,21 @@
|
||||
return @(model);
|
||||
}
|
||||
|
||||
+ (NSString *)santaProductVersion {
|
||||
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
|
||||
return info_dict[@"CFBundleShortVersionString"];
|
||||
}
|
||||
|
||||
+ (NSString *)santaBuildVersion {
|
||||
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
|
||||
return [[info_dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
|
||||
}
|
||||
|
||||
+ (NSString *)santaFullVersion {
|
||||
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
|
||||
return info_dict[@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
+ (NSDictionary *)_systemVersionDictionary {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
|
||||
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
|
||||
|
||||
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
|
||||
/// Protocol implemented by santabundleservice and utilized by SantaGUI for bundle hashing
|
||||
@protocol SNTBundleServiceXPC
|
||||
|
||||
///
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTRuleIdentifiers.h"
|
||||
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
|
||||
|
||||
///
|
||||
@@ -28,14 +29,13 @@
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
ruleCleanup:(SNTRuleCleanup)cleanupType
|
||||
reply:(void (^)(NSError *error))reply;
|
||||
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseRuleForIdentifiers:(SNTRuleIdentifiers *)identifiers
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
- (void)retrieveAllRules:(void (^)(NSArray<SNTRule *> *rules, NSError *error))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
@@ -43,7 +43,7 @@
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)(void))reply;
|
||||
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
|
||||
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
|
||||
- (void)setSyncTypeRequired:(SNTSyncType)syncType reply:(void (^)(void))reply;
|
||||
- (void)setAllowedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
|
||||
- (void)setBlockedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
|
||||
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
@@ -52,6 +52,7 @@
|
||||
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
- (void)setEnableAllEventUpload:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
- (void)setDisableUnknownEventUpload:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
- (void)setOverrideFileAccessAction:(NSString *)action reply:(void (^)(void))reply;
|
||||
|
||||
///
|
||||
/// Syncd Ops
|
||||
|
||||
@@ -34,8 +34,7 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
#else
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", cs.teamID, kBundleID];
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -50,9 +49,14 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
ofReply:YES];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTRule class], nil]
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
forSelector:@selector(databaseRuleAddRules:ruleCleanup:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTRule class], nil]
|
||||
forSelector:@selector(retrieveAllRules:)
|
||||
argumentIndex:0
|
||||
ofReply:YES];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
|
||||
@@ -17,13 +17,20 @@
|
||||
#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
|
||||
customMessage:(NSString *)message
|
||||
customURL:(NSString *)url
|
||||
customText:(NSString *)text API_AVAILABLE(macos(13.0));
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
// Pass true to isClean to perform a clean sync, defaults to false.
|
||||
//
|
||||
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
|
||||
isClean:(BOOL)cleanSync
|
||||
syncType:(SNTSyncType)syncType
|
||||
reply:(void (^)(SNTSyncStatusType))reply;
|
||||
|
||||
// Spindown the syncservice. The syncservice will not automatically start back up.
|
||||
|
||||
@@ -15,13 +15,24 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTRuleIdentifiers.h"
|
||||
#import "Source/common/SantaVnode.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@class MOLXPCConnection;
|
||||
|
||||
struct RuleCounts {
|
||||
int64_t binary;
|
||||
int64_t certificate;
|
||||
int64_t compiler;
|
||||
int64_t transitive;
|
||||
int64_t teamID;
|
||||
int64_t signingID;
|
||||
int64_t cdhash;
|
||||
};
|
||||
|
||||
///
|
||||
/// Protocol implemented by santad and utilized by santactl (unprivileged operations)
|
||||
///
|
||||
@@ -31,13 +42,12 @@
|
||||
/// 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;
|
||||
- (void)databaseRuleCounts:(void (^)(struct RuleCounts ruleCounts))reply;
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
- (void)staticRuleCount:(void (^)(int64_t count))reply;
|
||||
|
||||
@@ -47,28 +57,25 @@
|
||||
|
||||
///
|
||||
/// @param filePath A Path to the file, can be nil.
|
||||
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
|
||||
/// be calculated by this method from the filePath.
|
||||
/// @param certificateSHA256 A SHA256 hash of the signing certificate, can be nil.
|
||||
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
|
||||
/// returned. Binary rules take precedence over cert rules.
|
||||
/// @param identifiers The various identifiers to be used when making a decision.
|
||||
///
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID
|
||||
identifiers:(SNTRuleIdentifiers *)identifiers
|
||||
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;
|
||||
- (void)syncCleanRequired:(void (^)(BOOL))reply;
|
||||
- (void)syncTypeRequired:(void (^)(SNTSyncType))reply;
|
||||
- (void)enableBundles:(void (^)(BOOL))reply;
|
||||
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
|
||||
- (void)blockUSBMount:(void (^)(BOOL))reply;
|
||||
- (void)remountUSBMode:(void (^)(NSArray<NSString *> *))reply;
|
||||
|
||||
///
|
||||
/// Metrics ops
|
||||
|
||||
@@ -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"
|
||||
@@ -320,8 +320,8 @@ class SantaCache {
|
||||
Lock a bucket. Spins until the lock is acquired.
|
||||
*/
|
||||
inline void lock(struct bucket *bucket) const {
|
||||
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
||||
;
|
||||
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head)) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -245,7 +245,7 @@ struct S {
|
||||
uint64_t first_val;
|
||||
uint64_t second_val;
|
||||
|
||||
bool operator==(const S &rhs) {
|
||||
bool operator==(const S &rhs) const {
|
||||
return first_val == rhs.first_val && second_val == rhs.second_val;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
29
Source/common/ScopedCFTypeRef.h
Normal file
29
Source/common/ScopedCFTypeRef.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/// 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__SCOPEDCFTYPEREF_H
|
||||
#define SANTA__COMMON__SCOPEDCFTYPEREF_H
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include "Source/common/ScopedTypeRef.h"
|
||||
|
||||
namespace santa {
|
||||
|
||||
template <typename CFT>
|
||||
using ScopedCFTypeRef = ScopedTypeRef<CFT, (CFT)NULL, CFRetain, CFRelease>;
|
||||
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
141
Source/common/ScopedCFTypeRefTest.mm
Normal file
141
Source/common/ScopedCFTypeRefTest.mm
Normal file
@@ -0,0 +1,141 @@
|
||||
/// 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.
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#include "XCTest/XCTest.h"
|
||||
|
||||
#include "Source/common/ScopedCFTypeRef.h"
|
||||
|
||||
using santa::ScopedCFTypeRef;
|
||||
|
||||
@interface ScopedCFTypeRefTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ScopedCFTypeRefTest
|
||||
|
||||
- (void)testDefaultConstruction {
|
||||
// Default construction creates wraps a NULL object
|
||||
ScopedCFTypeRef<CFNumberRef> scopedRef;
|
||||
XCTAssertFalse(scopedRef.Unsafe());
|
||||
}
|
||||
|
||||
- (void)testOperatorBool {
|
||||
// Operator bool is `false` when object is null
|
||||
{
|
||||
ScopedCFTypeRef<CFNumberRef> scopedNullRef;
|
||||
XCTAssertFalse(scopedNullRef.Unsafe());
|
||||
XCTAssertFalse(scopedNullRef);
|
||||
}
|
||||
|
||||
// Operator bool is `true` when object is NOT null
|
||||
{
|
||||
int x = 123;
|
||||
CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &x);
|
||||
|
||||
ScopedCFTypeRef<CFNumberRef> scopedNumRef = ScopedCFTypeRef<CFNumberRef>::Assume(numRef);
|
||||
XCTAssertTrue(scopedNumRef.Unsafe());
|
||||
XCTAssertTrue(scopedNumRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that CFMutableArray is used for testing, even when subtypes aren't
|
||||
// needed, because it is never optimized into immortal constant values, unlike
|
||||
// other types.
|
||||
- (void)testAssume {
|
||||
int want = 123;
|
||||
int got = 0;
|
||||
CFMutableArrayRef array = CFArrayCreateMutable(nullptr, /*capacity=*/0, &kCFTypeArrayCallBacks);
|
||||
|
||||
// Baseline state, initial retain count is 1 after object creation
|
||||
XCTAssertEqual(1, CFGetRetainCount(array));
|
||||
|
||||
CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &want);
|
||||
CFArrayAppendValue(array, numRef);
|
||||
CFRelease(numRef);
|
||||
|
||||
XCTAssertEqual(1, CFArrayGetCount(array));
|
||||
|
||||
{
|
||||
ScopedCFTypeRef<CFMutableArrayRef> scopedArray =
|
||||
ScopedCFTypeRef<CFMutableArrayRef>::Assume(array);
|
||||
|
||||
// Ensure ownership was taken, and retain count remains unchanged
|
||||
XCTAssertTrue(scopedArray.Unsafe());
|
||||
XCTAssertEqual(1, CFGetRetainCount(scopedArray.Unsafe()));
|
||||
|
||||
// Make sure the object contains expected contents
|
||||
CFMutableArrayRef ref = scopedArray.Unsafe();
|
||||
XCTAssertEqual(1, CFArrayGetCount(ref));
|
||||
XCTAssertTrue(
|
||||
CFNumberGetValue((CFNumberRef)CFArrayGetValueAtIndex(ref, 0), kCFNumberIntType, &got));
|
||||
XCTAssertEqual(want, got);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that CFMutableArray is used for testing, even when subtypes aren't
|
||||
// needed, because it is never optimized into immortal constant values, unlike
|
||||
// other types.
|
||||
- (void)testRetain {
|
||||
int want = 123;
|
||||
int got = 0;
|
||||
CFMutableArrayRef array = CFArrayCreateMutable(nullptr, /*capacity=*/0, &kCFTypeArrayCallBacks);
|
||||
|
||||
// Baseline state, initial retain count is 1 after object creation
|
||||
XCTAssertEqual(1, CFGetRetainCount(array));
|
||||
|
||||
CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &want);
|
||||
CFArrayAppendValue(array, numRef);
|
||||
CFRelease(numRef);
|
||||
|
||||
XCTAssertEqual(1, CFArrayGetCount(array));
|
||||
|
||||
{
|
||||
ScopedCFTypeRef<CFMutableArrayRef> scopedArray =
|
||||
ScopedCFTypeRef<CFMutableArrayRef>::Retain(array);
|
||||
|
||||
// Ensure ownership was taken, and retain count was incremented
|
||||
XCTAssertTrue(scopedArray.Unsafe());
|
||||
XCTAssertEqual(2, CFGetRetainCount(scopedArray.Unsafe()));
|
||||
|
||||
// Make sure the object contains expected contents
|
||||
CFMutableArrayRef ref = scopedArray.Unsafe();
|
||||
XCTAssertEqual(1, CFArrayGetCount(ref));
|
||||
XCTAssertTrue(
|
||||
CFNumberGetValue((CFNumberRef)CFArrayGetValueAtIndex(ref, 0), kCFNumberIntType, &got));
|
||||
XCTAssertEqual(want, got);
|
||||
}
|
||||
|
||||
// The original `array` object should still be valid due to the extra retain.
|
||||
// Ensure the retain count has decreased since `scopedArray` went out of scope
|
||||
XCTAssertEqual(1, CFArrayGetCount(array));
|
||||
}
|
||||
|
||||
- (void)testInto {
|
||||
ScopedCFTypeRef<CFURLRef> scopedURLRef =
|
||||
ScopedCFTypeRef<CFURLRef>::Assume(CFURLCreateWithFileSystemPath(
|
||||
kCFAllocatorDefault, CFSTR("/usr/bin/true"), kCFURLPOSIXPathStyle, YES));
|
||||
|
||||
ScopedCFTypeRef<SecStaticCodeRef> scopedCodeRef;
|
||||
XCTAssertFalse(scopedCodeRef);
|
||||
|
||||
SecStaticCodeCreateWithPath(scopedURLRef.Unsafe(), kSecCSDefaultFlags,
|
||||
scopedCodeRef.InitializeInto());
|
||||
|
||||
// Ensure the scoped object was initialized
|
||||
XCTAssertTrue(scopedCodeRef);
|
||||
}
|
||||
|
||||
@end
|
||||
30
Source/common/ScopedIOObjectRef.h
Normal file
30
Source/common/ScopedIOObjectRef.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/// 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__SCOPEDIOOBJECTREF_H
|
||||
#define SANTA__COMMON__SCOPEDIOOBJECTREF_H
|
||||
|
||||
#include <IOKit/IOKitLib.h>
|
||||
|
||||
#include "Source/common/ScopedTypeRef.h"
|
||||
|
||||
namespace santa {
|
||||
|
||||
template <typename IOT>
|
||||
using ScopedIOObjectRef =
|
||||
ScopedTypeRef<IOT, (IOT)IO_OBJECT_NULL, IOObjectRetain, IOObjectRelease>;
|
||||
|
||||
}
|
||||
|
||||
#endif // namespace santa
|
||||
104
Source/common/ScopedIOObjectRefTest.mm
Normal file
104
Source/common/ScopedIOObjectRefTest.mm
Normal file
@@ -0,0 +1,104 @@
|
||||
/// 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.
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/usb/IOUSBLib.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Source/common/ScopedIOObjectRef.h"
|
||||
#include "Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h"
|
||||
|
||||
using santa::GetDefaultIOKitCommsPort;
|
||||
using santa::ScopedIOObjectRef;
|
||||
|
||||
@interface ScopedIOObjectRefTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ScopedIOObjectRefTest
|
||||
|
||||
- (void)testDefaultConstruction {
|
||||
// Default construction creates wraps a NULL object
|
||||
ScopedIOObjectRef<io_object_t> scopedRef;
|
||||
XCTAssertFalse(scopedRef.Unsafe());
|
||||
}
|
||||
|
||||
- (void)testOperatorBool {
|
||||
// Operator bool is `false` when object is null
|
||||
{
|
||||
ScopedIOObjectRef<io_object_t> scopedNullRef;
|
||||
XCTAssertFalse(scopedNullRef.Unsafe());
|
||||
XCTAssertFalse(scopedNullRef);
|
||||
}
|
||||
|
||||
// Operator bool is `true` when object is NOT null
|
||||
{
|
||||
CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
|
||||
XCTAssertNotEqual((CFMutableDictionaryRef)NULL, matchingDict);
|
||||
|
||||
io_service_t service = IOServiceGetMatchingService(GetDefaultIOKitCommsPort(), matchingDict);
|
||||
|
||||
ScopedIOObjectRef<io_service_t> scopedServiceRef =
|
||||
ScopedIOObjectRef<io_service_t>::Assume(service);
|
||||
|
||||
XCTAssertTrue(scopedServiceRef.Unsafe());
|
||||
XCTAssertTrue(scopedServiceRef);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testAssume {
|
||||
CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
|
||||
XCTAssertNotEqual((CFMutableDictionaryRef)NULL, matchingDict);
|
||||
|
||||
io_service_t service = IOServiceGetMatchingService(GetDefaultIOKitCommsPort(), matchingDict);
|
||||
|
||||
// Baseline state, initial retain count is 1 after object creation
|
||||
XCTAssertEqual(1, IOObjectGetUserRetainCount(service));
|
||||
XCTAssertNotEqual(IO_OBJECT_NULL, service);
|
||||
|
||||
{
|
||||
ScopedIOObjectRef<io_service_t> scopedIORef = ScopedIOObjectRef<io_service_t>::Assume(service);
|
||||
|
||||
// Ensure ownership was taken, and retain count remains unchanged
|
||||
XCTAssertTrue(scopedIORef.Unsafe());
|
||||
XCTAssertEqual(1, IOObjectGetUserRetainCount(scopedIORef.Unsafe()));
|
||||
XCTAssertNotEqual(IO_OBJECT_NULL, scopedIORef.Unsafe());
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRetain {
|
||||
CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
|
||||
XCTAssertNotEqual((CFMutableDictionaryRef)NULL, matchingDict);
|
||||
|
||||
io_service_t service = IOServiceGetMatchingService(GetDefaultIOKitCommsPort(), matchingDict);
|
||||
|
||||
// Baseline state, initial retain count is 1 after object creation
|
||||
XCTAssertEqual(1, IOObjectGetUserRetainCount(service));
|
||||
XCTAssertNotEqual(IO_OBJECT_NULL, service);
|
||||
|
||||
{
|
||||
ScopedIOObjectRef<io_service_t> scopedIORef = ScopedIOObjectRef<io_service_t>::Retain(service);
|
||||
|
||||
// Ensure ownership was taken, and retain count was incremented
|
||||
XCTAssertTrue(scopedIORef.Unsafe());
|
||||
XCTAssertEqual(2, IOObjectGetUserRetainCount(scopedIORef.Unsafe()));
|
||||
XCTAssertNotEqual(IO_OBJECT_NULL, scopedIORef.Unsafe());
|
||||
}
|
||||
|
||||
// The original `service` object should still be valid due to the extra retain.
|
||||
// Ensure the retain count has decreased since `scopedIORef` went out of scope.
|
||||
XCTAssertEqual(1, IOObjectGetUserRetainCount(service));
|
||||
}
|
||||
|
||||
@end
|
||||
80
Source/common/ScopedTypeRef.h
Normal file
80
Source/common/ScopedTypeRef.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/// 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__SCOPEDTYPEREF_H
|
||||
#define SANTA__COMMON__SCOPEDTYPEREF_H
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <assert.h>
|
||||
|
||||
namespace santa {
|
||||
|
||||
template <typename ElementT, ElementT InvalidV, auto RetainFunc,
|
||||
auto ReleaseFunc>
|
||||
class ScopedTypeRef {
|
||||
public:
|
||||
ScopedTypeRef() : object_(InvalidV) {}
|
||||
|
||||
// Can be implemented safely, but not currently needed
|
||||
ScopedTypeRef(ScopedTypeRef&& other) = delete;
|
||||
ScopedTypeRef& operator=(ScopedTypeRef&& rhs) = delete;
|
||||
ScopedTypeRef(const ScopedTypeRef& other) = delete;
|
||||
ScopedTypeRef& operator=(const ScopedTypeRef& other) = delete;
|
||||
|
||||
// Take ownership of a given object
|
||||
static ScopedTypeRef<ElementT, InvalidV, RetainFunc, ReleaseFunc> Assume(
|
||||
ElementT object) {
|
||||
return ScopedTypeRef<ElementT, InvalidV, RetainFunc, ReleaseFunc>(object);
|
||||
}
|
||||
|
||||
// Retain and take ownership of a given object
|
||||
static ScopedTypeRef<ElementT, InvalidV, RetainFunc, ReleaseFunc> Retain(
|
||||
ElementT object) {
|
||||
if (object) {
|
||||
RetainFunc(object);
|
||||
}
|
||||
return ScopedTypeRef<ElementT, InvalidV, RetainFunc, ReleaseFunc>(object);
|
||||
}
|
||||
|
||||
~ScopedTypeRef() {
|
||||
if (object_) {
|
||||
ReleaseFunc(object_);
|
||||
object_ = InvalidV;
|
||||
}
|
||||
}
|
||||
|
||||
explicit operator bool() { return object_ != InvalidV; }
|
||||
|
||||
ElementT Unsafe() { return object_; }
|
||||
|
||||
// This is to be used only to take ownership of objects that are created by
|
||||
// pass-by-pointer create functions. The object must not already be valid.
|
||||
// In non-opt builds, this is enforced by an assert that will terminate the
|
||||
// process.
|
||||
ElementT* InitializeInto() {
|
||||
assert(object_ == InvalidV);
|
||||
return &object_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Not API.
|
||||
// Use Assume or Retain static methods.
|
||||
ScopedTypeRef(ElementT object) : object_(object) {}
|
||||
|
||||
ElementT object_;
|
||||
};
|
||||
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
30
Source/common/SigningIDHelpers.h
Normal file
30
Source/common/SigningIDHelpers.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/// Copyright 2024 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/**
|
||||
Return a string representing normalized SigningID (prefixed with TeamID and a
|
||||
colon).
|
||||
|
||||
@param csc A MOLCodesignChecker instance
|
||||
|
||||
@return An NSString formated as teamID:signingID or nil if there isn't a valid signing ID.
|
||||
*/
|
||||
NSString *FormatSigningID(MOLCodesignChecker *csc);
|
||||
|
||||
__END_DECLS
|
||||
33
Source/common/SigningIDHelpers.m
Normal file
33
Source/common/SigningIDHelpers.m
Normal file
@@ -0,0 +1,33 @@
|
||||
/// Copyright 2024 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SigningIDHelpers.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
NSString *FormatSigningID(MOLCodesignChecker *csc) {
|
||||
if (!csc.signingID.length) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (!csc.teamID.length) {
|
||||
if (csc.platformBinary) {
|
||||
return [NSString stringWithFormat:@"%@:%@", @"platform", csc.signingID];
|
||||
} else {
|
||||
LOGD(@"unable to format signing ID missing team ID for non-platform binary");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%@:%@", csc.teamID, csc.signingID];
|
||||
}
|
||||
58
Source/common/String.h
Normal file
58
Source/common/String.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/// 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 <EndpointSecurity/ESTypes.h>
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace santa {
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
static inline NSString *OptionalStringToNSString(const std::optional<std::string> &optional_str) {
|
||||
std::string str = optional_str.value_or("");
|
||||
if (str.length() == 0) {
|
||||
return nil;
|
||||
} else {
|
||||
return StringToNSString(str);
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::string_view StringTokenToStringView(es_string_token_t es_str) {
|
||||
return std::string_view(es_str.data, es_str.length);
|
||||
}
|
||||
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
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),
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define NOBODY_UID ((unsigned int)-2)
|
||||
#define NOGROUP_GID ((unsigned int)-1)
|
||||
|
||||
@@ -38,23 +40,33 @@
|
||||
// Pretty print C++ string match errors
|
||||
#define XCTAssertCppStringEqual(got, want) XCTAssertCStringEqual((got).c_str(), (want).c_str())
|
||||
|
||||
#define XCTAssertCppStringBeginsWith(got, want) \
|
||||
XCTAssertTrue((got).rfind((want), 0) == 0, "\nPrefix not found.\n\t got: %s\n\twant: %s\n", \
|
||||
(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,12 +76,13 @@ 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,
|
||||
uint64_t future_deadline_ms = 100000);
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS();
|
||||
uint32_t MinSupportedESMessageVersion(es_event_type_t event_type);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
#include <time.h>
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
#include "Source/common/Platform.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{
|
||||
.val =
|
||||
@@ -59,7 +66,7 @@ struct stat MakeStat(int offset) {
|
||||
|
||||
es_string_token_t MakeESStringToken(const char *s) {
|
||||
return es_string_token_t{
|
||||
.length = strlen(s),
|
||||
.length = s ? strlen(s) : 0,
|
||||
.data = s,
|
||||
};
|
||||
}
|
||||
@@ -86,43 +93,10 @@ 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, *)) {
|
||||
return 6;
|
||||
} else if (@available(macOS 12.3, *)) {
|
||||
return 5;
|
||||
} else if (@available(macOS 11.0, *)) {
|
||||
return 4;
|
||||
} else if (@available(macOS 10.15.4, *)) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType action_type,
|
||||
uint64_t future_deadline_ms) {
|
||||
es_message_t es_msg = {
|
||||
.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,
|
||||
@@ -143,3 +117,204 @@ void SleepMS(long ms) {
|
||||
XCTAssertEqual(errno, EINTR);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
|
||||
// Note 1: This function only returns a subset of versions. This is due to the
|
||||
// minimum supported OS build version as well as features in latest versions
|
||||
// not currently being used. Capping the max means unnecessary duuplicate test
|
||||
// JSON files are not needed.
|
||||
//
|
||||
// Note 2: The following table maps ES message versions to lmin macOS version:
|
||||
// ES Version | macOS Version
|
||||
// 1 | 10.15.0
|
||||
// 2 | 10.15.4
|
||||
// 3 | Only in a beta
|
||||
// 4 | 11.0
|
||||
// 5 | 12.3
|
||||
// 6 | 13.0
|
||||
// 7 | 14.0
|
||||
// 8 | 15.0
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return 6;
|
||||
} else if (@available(macOS 12.3, *)) {
|
||||
return 5;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MinSupportedESMessageVersion(es_event_type_t event_type) {
|
||||
switch (event_type) {
|
||||
// The following events are available beginning in macOS 10.15
|
||||
case ES_EVENT_TYPE_AUTH_EXEC:
|
||||
case ES_EVENT_TYPE_AUTH_OPEN:
|
||||
case ES_EVENT_TYPE_AUTH_KEXTLOAD:
|
||||
case ES_EVENT_TYPE_AUTH_MMAP:
|
||||
case ES_EVENT_TYPE_AUTH_MPROTECT:
|
||||
case ES_EVENT_TYPE_AUTH_MOUNT:
|
||||
case ES_EVENT_TYPE_AUTH_RENAME:
|
||||
case ES_EVENT_TYPE_AUTH_SIGNAL:
|
||||
case ES_EVENT_TYPE_AUTH_UNLINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_EXEC:
|
||||
case ES_EVENT_TYPE_NOTIFY_OPEN:
|
||||
case ES_EVENT_TYPE_NOTIFY_FORK:
|
||||
case ES_EVENT_TYPE_NOTIFY_CLOSE:
|
||||
case ES_EVENT_TYPE_NOTIFY_CREATE:
|
||||
case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA:
|
||||
case ES_EVENT_TYPE_NOTIFY_EXIT:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK:
|
||||
case ES_EVENT_TYPE_NOTIFY_KEXTLOAD:
|
||||
case ES_EVENT_TYPE_NOTIFY_KEXTUNLOAD:
|
||||
case ES_EVENT_TYPE_NOTIFY_LINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_MMAP:
|
||||
case ES_EVENT_TYPE_NOTIFY_MPROTECT:
|
||||
case ES_EVENT_TYPE_NOTIFY_MOUNT:
|
||||
case ES_EVENT_TYPE_NOTIFY_UNMOUNT:
|
||||
case ES_EVENT_TYPE_NOTIFY_IOKIT_OPEN:
|
||||
case ES_EVENT_TYPE_NOTIFY_RENAME:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETATTRLIST:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETFLAGS:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETMODE:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETOWNER:
|
||||
case ES_EVENT_TYPE_NOTIFY_SIGNAL:
|
||||
case ES_EVENT_TYPE_NOTIFY_UNLINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_WRITE:
|
||||
case ES_EVENT_TYPE_AUTH_FILE_PROVIDER_MATERIALIZE:
|
||||
case ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_MATERIALIZE:
|
||||
case ES_EVENT_TYPE_AUTH_FILE_PROVIDER_UPDATE:
|
||||
case ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_UPDATE:
|
||||
case ES_EVENT_TYPE_AUTH_READLINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_READLINK:
|
||||
case ES_EVENT_TYPE_AUTH_TRUNCATE:
|
||||
case ES_EVENT_TYPE_NOTIFY_TRUNCATE:
|
||||
case ES_EVENT_TYPE_AUTH_LINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_LOOKUP:
|
||||
case ES_EVENT_TYPE_AUTH_CREATE:
|
||||
case ES_EVENT_TYPE_AUTH_SETATTRLIST:
|
||||
case ES_EVENT_TYPE_AUTH_SETEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_SETFLAGS:
|
||||
case ES_EVENT_TYPE_AUTH_SETMODE:
|
||||
case ES_EVENT_TYPE_AUTH_SETOWNER: return 1;
|
||||
|
||||
// The following events are available beginning in macOS 10.15.1
|
||||
case ES_EVENT_TYPE_AUTH_CHDIR:
|
||||
case ES_EVENT_TYPE_NOTIFY_CHDIR:
|
||||
case ES_EVENT_TYPE_AUTH_GETATTRLIST:
|
||||
case ES_EVENT_TYPE_NOTIFY_GETATTRLIST:
|
||||
case ES_EVENT_TYPE_NOTIFY_STAT:
|
||||
case ES_EVENT_TYPE_NOTIFY_ACCESS:
|
||||
case ES_EVENT_TYPE_AUTH_CHROOT:
|
||||
case ES_EVENT_TYPE_NOTIFY_CHROOT:
|
||||
case ES_EVENT_TYPE_AUTH_UTIMES:
|
||||
case ES_EVENT_TYPE_NOTIFY_UTIMES:
|
||||
case ES_EVENT_TYPE_AUTH_CLONE:
|
||||
case ES_EVENT_TYPE_NOTIFY_CLONE:
|
||||
case ES_EVENT_TYPE_NOTIFY_FCNTL:
|
||||
case ES_EVENT_TYPE_AUTH_GETEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_GETEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_LISTEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_LISTEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_READDIR:
|
||||
case ES_EVENT_TYPE_NOTIFY_READDIR:
|
||||
case ES_EVENT_TYPE_AUTH_DELETEEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_DELETEEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_FSGETPATH:
|
||||
case ES_EVENT_TYPE_NOTIFY_FSGETPATH:
|
||||
case ES_EVENT_TYPE_NOTIFY_DUP:
|
||||
case ES_EVENT_TYPE_AUTH_SETTIME:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETTIME:
|
||||
case ES_EVENT_TYPE_NOTIFY_UIPC_BIND:
|
||||
case ES_EVENT_TYPE_AUTH_UIPC_BIND:
|
||||
case ES_EVENT_TYPE_NOTIFY_UIPC_CONNECT:
|
||||
case ES_EVENT_TYPE_AUTH_UIPC_CONNECT:
|
||||
case ES_EVENT_TYPE_AUTH_EXCHANGEDATA:
|
||||
case ES_EVENT_TYPE_AUTH_SETACL:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETACL: return 1;
|
||||
|
||||
// The following events are available beginning in macOS 10.15.4
|
||||
case ES_EVENT_TYPE_NOTIFY_PTY_GRANT:
|
||||
case ES_EVENT_TYPE_NOTIFY_PTY_CLOSE:
|
||||
case ES_EVENT_TYPE_AUTH_PROC_CHECK:
|
||||
case ES_EVENT_TYPE_NOTIFY_PROC_CHECK:
|
||||
case ES_EVENT_TYPE_AUTH_GET_TASK: return 2;
|
||||
|
||||
// The following events are available beginning in macOS 11.0
|
||||
case ES_EVENT_TYPE_AUTH_SEARCHFS:
|
||||
case ES_EVENT_TYPE_NOTIFY_SEARCHFS:
|
||||
case ES_EVENT_TYPE_AUTH_FCNTL:
|
||||
case ES_EVENT_TYPE_AUTH_IOKIT_OPEN:
|
||||
case ES_EVENT_TYPE_AUTH_PROC_SUSPEND_RESUME:
|
||||
case ES_EVENT_TYPE_NOTIFY_PROC_SUSPEND_RESUME:
|
||||
case ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK_NAME:
|
||||
case ES_EVENT_TYPE_NOTIFY_TRACE:
|
||||
case ES_EVENT_TYPE_NOTIFY_REMOTE_THREAD_CREATE:
|
||||
case ES_EVENT_TYPE_AUTH_REMOUNT:
|
||||
case ES_EVENT_TYPE_NOTIFY_REMOUNT: return 4;
|
||||
|
||||
// The following events are available beginning in macOS 11.3
|
||||
case ES_EVENT_TYPE_AUTH_GET_TASK_READ:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK_READ:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK_INSPECT: return 4;
|
||||
|
||||
// The following events are available beginning in macOS 12.0
|
||||
case ES_EVENT_TYPE_NOTIFY_SETUID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETGID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETEUID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETEGID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETREUID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETREGID:
|
||||
case ES_EVENT_TYPE_AUTH_COPYFILE:
|
||||
case ES_EVENT_TYPE_NOTIFY_COPYFILE: return 4;
|
||||
|
||||
#if HAVE_MACOS_13
|
||||
// The following events are available beginning in macOS 13.0
|
||||
case ES_EVENT_TYPE_NOTIFY_AUTHENTICATION:
|
||||
case ES_EVENT_TYPE_NOTIFY_XP_MALWARE_DETECTED:
|
||||
case ES_EVENT_TYPE_NOTIFY_XP_MALWARE_REMEDIATED:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK:
|
||||
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH:
|
||||
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH:
|
||||
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN:
|
||||
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT:
|
||||
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN:
|
||||
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT:
|
||||
case ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_REMOVE: return 6;
|
||||
#endif
|
||||
|
||||
#if HAVE_MACOS_14
|
||||
// The following events are available beginning in macOS 14.0
|
||||
case ES_EVENT_TYPE_NOTIFY_PROFILE_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_PROFILE_REMOVE:
|
||||
case ES_EVENT_TYPE_NOTIFY_SU:
|
||||
case ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_PETITION:
|
||||
case ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_JUDGEMENT:
|
||||
case ES_EVENT_TYPE_NOTIFY_SUDO:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_REMOVE:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_SET:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_MODIFY_PASSWORD:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_DISABLE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ENABLE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_REMOVE:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_SET:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_CREATE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_CREATE_GROUP:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_DELETE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_DELETE_GROUP:
|
||||
case ES_EVENT_TYPE_NOTIFY_XPC_CONNECT: return 7;
|
||||
#endif
|
||||
|
||||
#if HAVE_MACOS_15
|
||||
case ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE: return 8;
|
||||
#endif
|
||||
|
||||
default: return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
#ifndef SANTA__COMMON__UNIT_H
|
||||
#define SANTA__COMMON__UNIT_H
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
struct Unit {};
|
||||
|
||||
} // namespace santa::common
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
//
|
||||
// !!! 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";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "Source/santad/ProcessTree/process_tree.proto";
|
||||
|
||||
option objc_class_prefix = "SNTPB";
|
||||
|
||||
@@ -177,6 +174,8 @@ message ProcessInfo {
|
||||
|
||||
// Time the process was started
|
||||
optional google.protobuf.Timestamp start_time = 17;
|
||||
|
||||
optional process_tree.Annotations annotations = 18;
|
||||
}
|
||||
|
||||
// Light variant of ProcessInfo message to help minimize on-disk/on-wire sizes
|
||||
@@ -206,6 +205,8 @@ message ProcessInfoLight {
|
||||
|
||||
// File information for the executable backing this process
|
||||
optional FileInfoLight executable = 10;
|
||||
|
||||
optional process_tree.Annotations annotations = 11;
|
||||
}
|
||||
|
||||
// Certificate information
|
||||
@@ -217,6 +218,27 @@ message CertificateInfo {
|
||||
optional string common_name = 2;
|
||||
}
|
||||
|
||||
// Information about a single entitlement key/value pair
|
||||
message Entitlement {
|
||||
// The name of an entitlement
|
||||
optional string key = 1;
|
||||
|
||||
// The value of an entitlement
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
// Information about entitlements
|
||||
message EntitlementInfo {
|
||||
// Whether or not the set of reported entilements is complete or has been
|
||||
// filtered (e.g. by configuration or clipped because too many to log).
|
||||
optional bool entitlements_filtered = 1;
|
||||
|
||||
// The set of entitlements associated with the target executable
|
||||
// Only top level keys are represented
|
||||
// Values (including nested keys) are JSON serialized
|
||||
repeated Entitlement entitlements = 2;
|
||||
}
|
||||
|
||||
// Information about a process execution event
|
||||
message Execution {
|
||||
// The process that executed the new image (e.g. the process that called
|
||||
@@ -235,10 +257,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 +288,8 @@ message Execution {
|
||||
REASON_TRANSITIVE = 8;
|
||||
REASON_LONG_PATH = 9;
|
||||
REASON_NOT_RUNNING = 10;
|
||||
REASON_SIGNING_ID = 11;
|
||||
REASON_CDHASH = 12;
|
||||
}
|
||||
optional Reason reason = 10;
|
||||
|
||||
@@ -289,6 +313,9 @@ message Execution {
|
||||
// The original path on disk of the target executable
|
||||
// Applies when executables are translocated
|
||||
optional string original_path = 15;
|
||||
|
||||
// Entitlement information about the target executbale
|
||||
optional EntitlementInfo entitlement_info = 16;
|
||||
}
|
||||
|
||||
// Information about a fork event
|
||||
@@ -384,6 +411,11 @@ message Unlink {
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
// Information about a processes codesigning invalidation event
|
||||
message CodesigningInvalidated {
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
}
|
||||
|
||||
// Information about a link event
|
||||
message Link {
|
||||
// The process performing the link
|
||||
@@ -432,6 +464,9 @@ message Disk {
|
||||
|
||||
// Time device appeared/disappeared
|
||||
optional google.protobuf.Timestamp appearance = 10;
|
||||
|
||||
// Path mounted from
|
||||
optional string mount_from = 11;
|
||||
}
|
||||
|
||||
// Information emitted when Santa captures bundle information
|
||||
@@ -464,6 +499,264 @@ 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;
|
||||
}
|
||||
|
||||
// Session identifier for a graphical session
|
||||
// Note: Identifiers are opaque and have no meaning outside of correlating Santa
|
||||
// events with the same identifier
|
||||
message GraphicalSession {
|
||||
optional uint32 id = 1;
|
||||
}
|
||||
|
||||
// Information about a socket address and its type
|
||||
message SocketAddress {
|
||||
// The socket address
|
||||
optional bytes address = 1;
|
||||
|
||||
enum Type {
|
||||
TYPE_UNKNOWN = 0;
|
||||
TYPE_NONE = 1;
|
||||
TYPE_IPV4 = 2;
|
||||
TYPE_IPV6 = 3;
|
||||
TYPE_NAMED_SOCKET = 4;
|
||||
}
|
||||
|
||||
// The type of the socket address
|
||||
optional Type type = 2;
|
||||
}
|
||||
|
||||
// Information about a user logging in via loginwindow
|
||||
message LoginWindowSessionLogin {
|
||||
// The process that emitted the login event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user logging in
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about a user logging out via loginwindow
|
||||
message LoginWindowSessionLogout {
|
||||
// The process that emitted the logout event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user logging out
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about a user locking their session via loginwindow
|
||||
message LoginWindowSessionLock {
|
||||
// The process that emitted the lock event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user locking their session
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about a user unlocking their session via loginwindow
|
||||
message LoginWindowSessionUnlock {
|
||||
// The process that emitted the unlock event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user unlocking their session
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about loginwindow events
|
||||
message LoginWindowSession {
|
||||
oneof event {
|
||||
LoginWindowSessionLogin login = 1;
|
||||
LoginWindowSessionLogout logout = 2;
|
||||
LoginWindowSessionLock lock = 3;
|
||||
LoginWindowSessionUnlock unlock = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Information about a login event from the `login(1)` utility
|
||||
message Login {
|
||||
// The process that emitted the login event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Whether or not the login was successful
|
||||
optional bool success = 2;
|
||||
|
||||
// Login failure message, if applicable
|
||||
optional bytes failure_message = 3;
|
||||
|
||||
// Information about the user that attempted to log in
|
||||
// Note: `uid` data may not always exist on failed attempts
|
||||
optional UserInfo user = 4;
|
||||
}
|
||||
|
||||
// Information about a logout event from the `login(1)` utility
|
||||
message Logout {
|
||||
// The process that emitted the logout event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Information about the user that logged out
|
||||
optional UserInfo user = 2;
|
||||
}
|
||||
|
||||
// Information about login and logout events from the `login(1)` utility
|
||||
message LoginLogout {
|
||||
oneof event {
|
||||
Login login = 1;
|
||||
Logout logout = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Information related to Screen Sharing attaching to a graphical session
|
||||
message ScreenSharingAttach {
|
||||
// The process that emitted the attach event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Whether or not the attach was successful
|
||||
optional bool success = 2;
|
||||
|
||||
// Source address information
|
||||
optional SocketAddress source = 3;
|
||||
|
||||
// Apple ID of the viewer
|
||||
optional bytes viewer = 4;
|
||||
|
||||
// Type of authentication used
|
||||
optional bytes authentication_type = 5;
|
||||
|
||||
// User that attempted authentication, if applicable
|
||||
optional UserInfo authentication_user = 6;
|
||||
|
||||
// Username of the loginwindow session, if available
|
||||
optional UserInfo session_user = 7;
|
||||
|
||||
// Whether or not there was an existing session
|
||||
optional bool existing_session = 8;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 9;
|
||||
}
|
||||
|
||||
// Information related to Screen Sharing detaching from a graphical session
|
||||
message ScreenSharingDetach {
|
||||
// The process that emitted the detach event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Source address information
|
||||
optional SocketAddress source = 2;
|
||||
|
||||
// Apple ID of the viewer
|
||||
optional bytes viewer = 3;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 4;
|
||||
}
|
||||
|
||||
// Information about Screen Sharing attach and detach events
|
||||
message ScreenSharing {
|
||||
oneof event {
|
||||
ScreenSharingAttach attach = 1;
|
||||
ScreenSharingDetach detach = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Information about SSH login events from the macOS OpenSSH implementation
|
||||
message OpenSSHLogin {
|
||||
// The process that emitted the login event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
enum Result {
|
||||
RESULT_UNKNOWN = 0;
|
||||
RESULT_LOGIN_EXCEED_MAXTRIES = 1;
|
||||
RESULT_LOGIN_ROOT_DENIED = 2;
|
||||
RESULT_AUTH_SUCCESS = 3;
|
||||
RESULT_AUTH_FAIL_NONE = 4;
|
||||
RESULT_AUTH_FAIL_PASSWD = 5;
|
||||
RESULT_AUTH_FAIL_KBDINT = 6;
|
||||
RESULT_AUTH_FAIL_PUBKEY = 7;
|
||||
RESULT_AUTH_FAIL_HOSTBASED = 8;
|
||||
RESULT_AUTH_FAIL_GSSAPI = 9;
|
||||
RESULT_INVALID_USER = 10;
|
||||
}
|
||||
|
||||
// The result of the login attempt
|
||||
// Note: Successful if type == `RESULT_AUTH_SUCCESS`
|
||||
optional Result result = 2;
|
||||
|
||||
// Source address of the connection
|
||||
optional SocketAddress source = 3;
|
||||
|
||||
// Name of the user that attempted to login
|
||||
// Note: `uid` data may not always exist on failed attempts
|
||||
optional UserInfo user = 4;
|
||||
}
|
||||
|
||||
// Information about SSH logout events from the macOS OpenSSH implementation
|
||||
message OpenSSHLogout {
|
||||
// The process that emitted the logout event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Source address of the connection
|
||||
optional SocketAddress source = 2;
|
||||
|
||||
// Information about the user that logged out
|
||||
optional UserInfo user = 3;
|
||||
}
|
||||
|
||||
// Information about login/logout events from the macOS OpenSSH implementation
|
||||
message OpenSSH {
|
||||
oneof event {
|
||||
OpenSSHLogin login = 1;
|
||||
OpenSSHLogout logout = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// A message encapsulating a single event
|
||||
message SantaMessage {
|
||||
// Machine ID of the host emitting this log
|
||||
@@ -489,6 +782,12 @@ message SantaMessage {
|
||||
Disk disk = 18;
|
||||
Bundle bundle = 19;
|
||||
Allowlist allowlist = 20;
|
||||
FileAccess file_access = 21;
|
||||
CodesigningInvalidated codesigning_invalidated = 22;
|
||||
LoginWindowSession login_window_session = 23;
|
||||
LoginLogout login_logout = 24;
|
||||
ScreenSharing screen_sharing = 25;
|
||||
OpenSSH open_ssh = 26;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,14 @@ objc_library(
|
||||
"UserNotifications",
|
||||
],
|
||||
deps = [
|
||||
":SNTAboutWindowView",
|
||||
":SNTDeviceMessageWindowView",
|
||||
":SNTFileAccessMessageWindowView",
|
||||
"//Source/common:CertificateHelpers",
|
||||
"//Source/common:SNTBlockMessage_SantaGUI",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTStrengthify",
|
||||
@@ -85,7 +119,7 @@ macos_application(
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.15",
|
||||
minimum_os_version = "12.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>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -13,23 +13,24 @@
|
||||
<outlet property="foundFileCountLabel" destination="LHV-gV-vyf" id="Sr0-T2-xGx"/>
|
||||
<outlet property="hashingIndicator" destination="VyY-Yg-JOe" id="Yq4-tZ-9ep"/>
|
||||
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
|
||||
<outlet property="dismissEventButton" destination="BbV-3h-mmL" id="5s4-ZB-xlo"/>
|
||||
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" titled="YES" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1916" height="1099"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="540" height="462"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
|
||||
<rect key="frame" x="16" y="434" width="37" height="32"/>
|
||||
<rect key="frame" x="16" y="434" width="37" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
|
||||
<buttonCell key="cell" type="roundTextured" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="texturedRounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
@@ -128,10 +129,6 @@
|
||||
</textField>
|
||||
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
|
||||
<rect key="frame" x="62" y="235" width="15" height="15"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSInfo" imagePosition="overlaps" alignment="center" refusesFirstResponder="YES" imageScaling="proportionallyDown" inset="2" id="R72-Qy-Xbb">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -139,6 +136,10 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</buttonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
|
||||
<binding destination="-2" name="hidden" keyPath="self.publisherInfo" id="fFR-f3-Oiw">
|
||||
@@ -241,16 +242,17 @@
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="147" y="30" width="126" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="push" title="Open Event..." bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<buttonCell key="cell" type="push" title="Open..." bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</buttonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
|
||||
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
|
||||
@@ -272,30 +274,30 @@ DQ
|
||||
</textField>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
|
||||
<rect key="frame" x="110" y="80" width="319" height="29"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
</buttonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="271" y="28" width="124" height="34"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="push" title="Ignore" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<buttonCell key="cell" type="push" title="Dismiss" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
Gw
|
||||
</string>
|
||||
</buttonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<accessibility description="Dismiss Dialog"/>
|
||||
<connections>
|
||||
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
|
||||
|
||||
@@ -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;
|
||||
@@ -52,6 +54,11 @@
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *openEventButton;
|
||||
|
||||
///
|
||||
/// Reference to the "Dismiss Event" button in the XIB. Used to update its title.
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *dismissEventButton;
|
||||
|
||||
///
|
||||
/// The execution event that this window is for
|
||||
///
|
||||
|
||||
@@ -17,15 +17,18 @@
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "Source/common/CertificateHelpers.h"
|
||||
#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 +43,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,15 +81,29 @@
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event customURL:self.customURL];
|
||||
|
||||
if (!url) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
} else if (self.customURL.length == 0) {
|
||||
// Set the button text only if a per-rule custom URL is not used. If a
|
||||
// custom URL is used, it is assumed that the `EventDetailText` config value
|
||||
// does not apply and the default text will be used.
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
// Require the button keyEquivalent set to be CMD + Return
|
||||
[self.openEventButton setKeyEquivalent:@"\r"]; // Return Key
|
||||
[self.openEventButton
|
||||
setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; // Command Key
|
||||
}
|
||||
}
|
||||
|
||||
NSString *dismissButtonText = [[SNTConfigurator configurator] dismissText];
|
||||
if (dismissButtonText.length) {
|
||||
[self.dismissEventButton setTitle:dismissButtonText];
|
||||
}
|
||||
|
||||
if (!self.event.needsBundleHash) {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.hashingIndicator removeFromSuperview];
|
||||
@@ -107,21 +127,17 @@
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender {
|
||||
// SFCertificatePanel expects an NSArray of SecCertificateRef's
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
|
||||
for (MOLCertificate *cert in self.event.signingChain) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
|
||||
modalDelegate:nil
|
||||
didEndSelector:nil
|
||||
contextInfo:nil
|
||||
certificates:certArray
|
||||
certificates:CertificateChain(self.event.signingChain)
|
||||
showGroup:YES];
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
@@ -137,19 +153,7 @@
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
MOLCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if ([leafCert.commonName isEqualToString:@"Apple Mac OS Application Signing"]) {
|
||||
return [NSString stringWithFormat:@"App Store (Team ID: %@)", self.event.teamID];
|
||||
} else if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
return Publisher(self.event.signingChain, self.event.teamID);
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user