mirror of
https://github.com/google/santa.git
synced 2026-01-15 09:17:59 -05:00
Compare commits
358 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41c918ee87 | ||
|
|
1adb6d2726 | ||
|
|
8c531a256b | ||
|
|
5829363733 | ||
|
|
379f283c62 | ||
|
|
2082345c02 | ||
|
|
dd8f81a60e | ||
|
|
8ccb0813f1 | ||
|
|
b24e7e42bf | ||
|
|
4821ebebd5 | ||
|
|
efeaa82618 | ||
|
|
3f3de02644 | ||
|
|
f6c9456ea7 | ||
|
|
2aaff051c8 | ||
|
|
2df7e91c87 | ||
|
|
37644acd01 | ||
|
|
899ca89e23 | ||
|
|
e7281f1c55 | ||
|
|
bf0ca24ae7 | ||
|
|
4fe8b7908f | ||
|
|
a8dd332402 | ||
|
|
6631b0a8e3 | ||
|
|
07e09db608 | ||
|
|
d041a48c97 | ||
|
|
1683e09cc8 | ||
|
|
d6c73e0c6c | ||
|
|
72969a3c92 | ||
|
|
d2dbed78dd | ||
|
|
8fa91e4ff0 | ||
|
|
551763146d | ||
|
|
7a7f0cd5a8 | ||
|
|
fcb49701b3 | ||
|
|
c9ef723fc5 | ||
|
|
dc6732ef04 | ||
|
|
a48900a4ae | ||
|
|
bb49118d94 | ||
|
|
456333d6d2 | ||
|
|
fd23a5c3b7 | ||
|
|
ec203e8796 | ||
|
|
57ff69208d | ||
|
|
f00b7d2ded | ||
|
|
9791fdd53c | ||
|
|
26e2203f1e | ||
|
|
4a47195d12 | ||
|
|
4436e221df | ||
|
|
deccc8a148 | ||
|
|
06da796a4d | ||
|
|
7b99a76d0d | ||
|
|
c2d3e99446 | ||
|
|
6db7fea8ae | ||
|
|
6fcb4cfe63 | ||
|
|
8b55ee4da5 | ||
|
|
cc3177502c | ||
|
|
a49a59b109 | ||
|
|
2c06c39c82 | ||
|
|
234f81ea7c | ||
|
|
743c567bf8 | ||
|
|
21220f1499 | ||
|
|
39f3ffe8fc | ||
|
|
fdb01928a0 | ||
|
|
fbefbc5910 | ||
|
|
9db00d143d | ||
|
|
1cc40d59d8 | ||
|
|
ba1ace56f0 | ||
|
|
6d911e9d6e | ||
|
|
7e2b291122 | ||
|
|
64096f5d08 | ||
|
|
aec1c74fab | ||
|
|
d4a0d77cb9 | ||
|
|
7df209ed3f | ||
|
|
b7421e4499 | ||
|
|
e044fe3601 | ||
|
|
a67801d5ed | ||
|
|
3d37a3a5ae | ||
|
|
bfae5dc828 | ||
|
|
fde5f52a11 | ||
|
|
01bd1bfdca | ||
|
|
ae13900676 | ||
|
|
a65c91874b | ||
|
|
6a3fda069c | ||
|
|
4d34099142 | ||
|
|
e639574973 | ||
|
|
636f9ea873 | ||
|
|
9099409915 | ||
|
|
976f483a99 | ||
|
|
8a32b7a56b | ||
|
|
7eeb06b406 | ||
|
|
4540a1c656 | ||
|
|
acc7b32b24 | ||
|
|
b92d513f5d | ||
|
|
3458fccd4e | ||
|
|
fdfb00368c | ||
|
|
6bd369cfb2 | ||
|
|
0df26c6214 | ||
|
|
6e22da1d97 | ||
|
|
1725809335 | ||
|
|
3eff49feda | ||
|
|
5caedebb06 | ||
|
|
d823028b72 | ||
|
|
49b2d6e22a | ||
|
|
4236d57e96 | ||
|
|
36d463a1dc | ||
|
|
adbafd6bab | ||
|
|
b5ebe1259c | ||
|
|
e0ae0f481b | ||
|
|
8037c79fc0 | ||
|
|
892d303de1 | ||
|
|
ff3979263e | ||
|
|
01afefd3d4 | ||
|
|
830627e7bc | ||
|
|
601d726fcc | ||
|
|
0be1ca0199 | ||
|
|
8602593149 | ||
|
|
9bca601ce6 | ||
|
|
c73acd59d4 | ||
|
|
3c334e8882 | ||
|
|
5f811cadf8 | ||
|
|
4252475de0 | ||
|
|
45f1822681 | ||
|
|
498a23d907 | ||
|
|
5dff8a18f4 | ||
|
|
676c02626d | ||
|
|
64950d0a99 | ||
|
|
16f74cb85c | ||
|
|
aadc961429 | ||
|
|
be66fd92f4 | ||
|
|
feea349f25 | ||
|
|
1c04c3a257 | ||
|
|
818d3f645f | ||
|
|
15d6bb1f14 | ||
|
|
211dbd123f | ||
|
|
c67364fe76 | ||
|
|
2043983f69 | ||
|
|
2f408936a0 | ||
|
|
02c1d0f267 | ||
|
|
4728c346cc | ||
|
|
9588dd8a0e | ||
|
|
e3e48aed1b | ||
|
|
e60f9cf6c5 | ||
|
|
c7e309ccb1 | ||
|
|
ad8aafbd07 | ||
|
|
9e671c3dee | ||
|
|
d97abe36f2 | ||
|
|
faa8946056 | ||
|
|
8b2b1f0bfc | ||
|
|
16678cd5a0 | ||
|
|
0bd6a199a3 | ||
|
|
58e2b7e1b8 | ||
|
|
b824a8e3e0 | ||
|
|
25bf2a93e4 | ||
|
|
f1ea1b369f | ||
|
|
5503a88308 | ||
|
|
8cf0f8217d | ||
|
|
22799ffc2a | ||
|
|
cb61d0cc99 | ||
|
|
fb7447ceba | ||
|
|
45e51e9c09 | ||
|
|
b0f0cdd4e6 | ||
|
|
65090d3ef2 | ||
|
|
9c80f79d82 | ||
|
|
93adaea81e | ||
|
|
a125b340a5 | ||
|
|
fbd0de3d48 | ||
|
|
6f2ae62bce | ||
|
|
da29b20473 | ||
|
|
197109a8ee | ||
|
|
91f3168c7a | ||
|
|
a00ec41518 | ||
|
|
c32248aaf7 | ||
|
|
afd97bdf3e | ||
|
|
73c4875b1f | ||
|
|
916fc8c0e6 | ||
|
|
e59e6105f3 | ||
|
|
216ac811eb | ||
|
|
48f92f5913 | ||
|
|
6bb08d0490 | ||
|
|
82b71c0f20 | ||
|
|
10ccee9e4c | ||
|
|
acbbb9e7b0 | ||
|
|
3939ad9813 | ||
|
|
d20455252d | ||
|
|
5cd901034f | ||
|
|
4e82392370 | ||
|
|
19710f7233 | ||
|
|
27e32bd9ff | ||
|
|
c268ad4f9a | ||
|
|
f7a1a4cb39 | ||
|
|
ad6e03e6cc | ||
|
|
8ecc3f879a | ||
|
|
d51093501c | ||
|
|
05dd1b6215 | ||
|
|
8c3320e3e9 | ||
|
|
369dc9a63c | ||
|
|
7adc55007c | ||
|
|
fe6be921d3 | ||
|
|
23b31ec413 | ||
|
|
727b009a1c | ||
|
|
1c42f06135 | ||
|
|
e1cf8e70a3 | ||
|
|
7a500b8135 | ||
|
|
3702af0309 | ||
|
|
697cd29a0a | ||
|
|
5735a12424 | ||
|
|
07b8f2121d | ||
|
|
78a1a929fd | ||
|
|
9163417b54 | ||
|
|
fa6630a31a | ||
|
|
1f2b82fc58 | ||
|
|
b77b0142af | ||
|
|
2f80a42845 | ||
|
|
67db370492 | ||
|
|
a0319ecf52 | ||
|
|
16d0bd6db6 | ||
|
|
9e3943ec68 | ||
|
|
e461b4bfbc | ||
|
|
8f836afe86 | ||
|
|
04ad1c34ba | ||
|
|
c3042e21dc | ||
|
|
3ede20a121 | ||
|
|
976118cce4 | ||
|
|
ea85f0f539 | ||
|
|
d193b05057 | ||
|
|
9fb4f2e171 | ||
|
|
58cec5819a | ||
|
|
6ba5831f2d | ||
|
|
a22e3ead83 | ||
|
|
2611b551ce | ||
|
|
023f96f5c8 | ||
|
|
1523d58429 | ||
|
|
81049db170 | ||
|
|
c110245701 | ||
|
|
d7a56b9bd4 | ||
|
|
4bb5804a6f | ||
|
|
e68fb7235a | ||
|
|
f93e7ef879 | ||
|
|
f472f4821c | ||
|
|
1c97761038 | ||
|
|
e569a684b7 | ||
|
|
66c32dc526 | ||
|
|
075d3cbc11 | ||
|
|
340326df8a | ||
|
|
f52edd2a76 | ||
|
|
11c247e33a | ||
|
|
a859b9b341 | ||
|
|
c190f1f52d | ||
|
|
87dc191494 | ||
|
|
3a19591822 | ||
|
|
b225c0740e | ||
|
|
d1fffb4636 | ||
|
|
9d7ca62e46 | ||
|
|
2a6073a9a1 | ||
|
|
296f06582b | ||
|
|
0e27dab4c6 | ||
|
|
256836d7f8 | ||
|
|
b117d8106e | ||
|
|
c980223215 | ||
|
|
635b33ebf9 | ||
|
|
b6f35c9b9f | ||
|
|
796109cc60 | ||
|
|
38f580de72 | ||
|
|
c7a58c77e7 | ||
|
|
9a4fe782d7 | ||
|
|
fbb5f3728f | ||
|
|
24b96c4798 | ||
|
|
1edf6d9200 | ||
|
|
ac1f8ea1b8 | ||
|
|
9923f601b6 | ||
|
|
471ae89406 | ||
|
|
54d6653973 | ||
|
|
27ee66597b | ||
|
|
10f2d852f5 | ||
|
|
1fcb63dc92 | ||
|
|
7944f681f8 | ||
|
|
e3aedc92ba | ||
|
|
d2b6c2b6c2 | ||
|
|
d026989dfb | ||
|
|
e7a8e9b6ac | ||
|
|
1d9af01353 | ||
|
|
9c6af7fc03 | ||
|
|
543b1a29fe | ||
|
|
625ec67789 | ||
|
|
c5696d71e7 | ||
|
|
5f3cef52de | ||
|
|
eeed0b5aa6 | ||
|
|
9ef171e663 | ||
|
|
ad1868a50f | ||
|
|
78643d3c49 | ||
|
|
8b22c85a64 | ||
|
|
58fe5d3d76 | ||
|
|
8b2227967e | ||
|
|
65693acea1 | ||
|
|
7cea383930 | ||
|
|
5ae2376158 | ||
|
|
e851337eac | ||
|
|
2e53834980 | ||
|
|
aef139e93c | ||
|
|
a9e5bf09a7 | ||
|
|
4ee3f281c3 | ||
|
|
462ce89d42 | ||
|
|
44117833c0 | ||
|
|
8b6e029da2 | ||
|
|
f183e246df | ||
|
|
c60a35f280 | ||
|
|
4f65965277 | ||
|
|
01e4e15b81 | ||
|
|
532cb37e0b | ||
|
|
9d379d3884 | ||
|
|
3e7a191bf7 | ||
|
|
c5a048f4d9 | ||
|
|
f4769bad90 | ||
|
|
254497ad15 | ||
|
|
0a83445838 | ||
|
|
eff287259e | ||
|
|
6f2c0e3457 | ||
|
|
38769f7cd1 | ||
|
|
fa785ad3c2 | ||
|
|
5dae0cabdd | ||
|
|
a8b4f4ea7e | ||
|
|
2221c93bbc | ||
|
|
d1c33baf35 | ||
|
|
d2bbdff373 | ||
|
|
db1d65f944 | ||
|
|
d17aeac2f4 | ||
|
|
7840270dd0 | ||
|
|
dcf44c9872 | ||
|
|
fc365c888f | ||
|
|
85f0782399 | ||
|
|
64bc34c302 | ||
|
|
e2fc4c735d | ||
|
|
ff9cb34490 | ||
|
|
60405f1e10 | ||
|
|
ac9d3b2adf | ||
|
|
7e8bd46da3 | ||
|
|
2f6ed455e5 | ||
|
|
8cb86b6d1d | ||
|
|
fc074f6014 | ||
|
|
a7856e60e8 | ||
|
|
41a40c9fbd | ||
|
|
8c18f6ebf5 | ||
|
|
949053fedd | ||
|
|
8d2c39b71d | ||
|
|
8f872fb4fc | ||
|
|
5512f8cf19 | ||
|
|
6742b38e31 | ||
|
|
d1635f7e11 | ||
|
|
e2b865c081 | ||
|
|
012b02de5d | ||
|
|
11ebead617 | ||
|
|
e3fbabfe37 | ||
|
|
8757da7822 | ||
|
|
428582f471 | ||
|
|
6e0effc0f4 | ||
|
|
683114fbec | ||
|
|
d9ebb4e3db | ||
|
|
e6aaf2f198 | ||
|
|
1c3757d4ab | ||
|
|
4346bb29c2 | ||
|
|
09655df8fc |
18
.allstar/binary_artifacts.yaml
Normal file
18
.allstar/binary_artifacts.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Ignore reason: These crafted binaries are used in tests
|
||||
ignorePaths:
|
||||
- Source/common/testdata/bad_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
- Source/common/testdata/32bitplist
|
||||
- Source/common/testdata/BundleExample.app/Contents/MacOS/BundleExample
|
||||
- Source/common/testdata/DirectoryBundle/Contents/MacOS/DirectoryBundle
|
||||
- Source/common/testdata/DirectoryBundle/Contents/Resources/BundleExample.app/Contents/MacOS/BundleExample
|
||||
- Source/santad/testdata/binaryrules/badbinary
|
||||
- Source/santad/testdata/binaryrules/goodbinary
|
||||
- Source/santad/testdata/binaryrules/badcert
|
||||
- Source/santad/testdata/binaryrules/banned_teamid_allowed_binary
|
||||
- Source/santad/testdata/binaryrules/banned_teamid
|
||||
- Source/santad/testdata/binaryrules/goodcert
|
||||
- Source/santad/testdata/binaryrules/noop
|
||||
- Source/santad/testdata/binaryrules/rules.db
|
||||
19
.bazelrc
19
.bazelrc
@@ -1,2 +1,19 @@
|
||||
build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
build --host_force_python=PY2
|
||||
|
||||
build --copt=-Werror
|
||||
build --copt=-Wall
|
||||
build --copt=-Wno-error=deprecated-declarations
|
||||
build --per_file_copt=.*\.mm\$@-std=c++17
|
||||
build --cxxopt=-std=c++17
|
||||
|
||||
build --copt=-DSANTA_OPEN_SOURCE=1
|
||||
build --cxxopt=-DSANTA_OPEN_SOURCE=1
|
||||
|
||||
build:asan --strip=never
|
||||
build:asan --copt="-Wno-macro-redefined"
|
||||
build:asan --copt="-D_FORTIFY_SOURCE=0"
|
||||
build:asan --copt="-O1"
|
||||
build:asan --copt="-fno-omit-frame-pointer"
|
||||
build:asan --copt="-fsanitize=address"
|
||||
build:asan --copt="-DADDRESS_SANITIZER"
|
||||
build:asan --linkopt="-fsanitize=address"
|
||||
|
||||
1
.bazelversion
Normal file
1
.bazelversion
Normal file
@@ -0,0 +1 @@
|
||||
5.3.0
|
||||
@@ -1,18 +1,18 @@
|
||||
Language: ObjC
|
||||
BasedOnStyle: Google
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
# Disable ColumnLimit because it causes some very weird line breaks.
|
||||
# For ObjC the limit is 100
|
||||
# For Cpp the limit is 80
|
||||
ColumnLimit: 0
|
||||
IndentWidth: 2
|
||||
ObjCBlockIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
|
||||
# For ObjC, the line limit is 100
|
||||
ColumnLimit: 100
|
||||
|
||||
# Allow short case statements to be on a single line
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
|
||||
# Ban short loops and functions on a single line
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
|
||||
# Allow spaces in NSArray/NSDictionary literals @[ and @{
|
||||
SpacesInContainerLiterals: true
|
||||
@@ -20,3 +20,13 @@ SpacesInContainerLiterals: true
|
||||
# For pointers, always put the * next to the variable name.
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
|
||||
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
BasedOnStyle: Google
|
||||
|
||||
# For C++, the line limit is 80
|
||||
ColumnLimit: 80
|
||||
|
||||
14
.github/workflows/check-markdown.yml
vendored
Normal file
14
.github/workflows/check-markdown.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Check Markdown
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**.md"
|
||||
|
||||
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'"
|
||||
56
.github/workflows/ci.yml
vendored
Normal file
56
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- 'Source/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'Source/**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run linters
|
||||
run: ./Testing/lint.sh
|
||||
|
||||
build_userspace:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=adhoc
|
||||
|
||||
unit_tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run All Tests
|
||||
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=adhoc --test_output=errors
|
||||
|
||||
test_coverage:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Generate test coverage
|
||||
run: sh ./generate_cov.sh
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ./bazel-out/_coverage/_coverage_report.dat
|
||||
flag-name: Unit
|
||||
13
.github/workflows/continuous.yml
vendored
Normal file
13
.github/workflows/continuous.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: continuous
|
||||
on:
|
||||
schedule:
|
||||
- cron: '* 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
|
||||
- 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
|
||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -1,9 +1,20 @@
|
||||
.DS_Store
|
||||
default.profraw
|
||||
*.profraw
|
||||
*.provisionprofile
|
||||
bazel-*
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
Santa.xcodeproj/project.xcworkspace
|
||||
Santa.xcworkspace/xcuserdata
|
||||
Santa.xcworkspace/xcshareddata
|
||||
Santa.xcodeproj/*
|
||||
Santa.xcworkspace/*
|
||||
CoverageData/*
|
||||
*.tulsiconf-user
|
||||
xcuserdata
|
||||
tulsigen-*
|
||||
*.crt
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.keychain
|
||||
*.swp
|
||||
compile_commands.json
|
||||
.cache/
|
||||
.vscode/*
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -1,15 +0,0 @@
|
||||
---
|
||||
os: osx
|
||||
osx_image: xcode11
|
||||
language: objective-c
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
homebrew:
|
||||
taps: bazelbuild/tap
|
||||
packages: bazelbuild/tap/bazel
|
||||
update: true
|
||||
|
||||
script:
|
||||
- bazel build :release --show_progress_rate_limit=30.0 -c opt --apple_generate_dsym --color=no --verbose_failures --sandbox_debug
|
||||
- bazel test :unit_tests --show_progress_rate_limit=30.0 --test_output=errors --color=no --verbose_failures --sandbox_debug
|
||||
91
BUILD
91
BUILD
@@ -1,8 +1,7 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
load("//:version.bzl", "SANTA_VERSION")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
@@ -11,8 +10,29 @@ exports_files(["LICENSE"])
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_version = SANTA_VERSION,
|
||||
short_version_string = SANTA_VERSION,
|
||||
build_label_pattern = ".*santa_{release}\\.{build}",
|
||||
build_version = "{release}.{build}",
|
||||
capture_groups = {
|
||||
"release": "\\d{4}\\.\\d+",
|
||||
"build": "\\d+",
|
||||
},
|
||||
fallback_build_label = "santa_9999.1.1",
|
||||
short_version_string = "{release}",
|
||||
)
|
||||
|
||||
# Used to detect release builds
|
||||
config_setting(
|
||||
name = "release_build",
|
||||
values = {"define": "SANTA_BUILD_TYPE=release"},
|
||||
visibility = [":santa_package_group"],
|
||||
)
|
||||
|
||||
# Adhoc signed - provisioning profiles are not used.
|
||||
# Used for CI runs and dev builds when SIP is disabled.
|
||||
config_setting(
|
||||
name = "adhoc_build",
|
||||
values = {"define": "SANTA_BUILD_TYPE=adhoc"},
|
||||
visibility = [":santa_package_group"],
|
||||
)
|
||||
|
||||
# Used to detect optimized builds
|
||||
@@ -21,6 +41,11 @@ config_setting(
|
||||
values = {"compilation_mode": "opt"},
|
||||
)
|
||||
|
||||
package_group(
|
||||
name = "santa_package_group",
|
||||
packages = ["//..."],
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Loading/Unloading/Reloading
|
||||
################################################################################
|
||||
@@ -29,7 +54,8 @@ run_command(
|
||||
cmd = """
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
|
||||
sudo kextunload -b com.google.santa-driver 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.syncservice.plist 2>/dev/null
|
||||
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
|
||||
""",
|
||||
)
|
||||
@@ -39,6 +65,8 @@ run_command(
|
||||
cmd = """
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
""",
|
||||
)
|
||||
@@ -46,17 +74,14 @@ launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
run_command(
|
||||
name = "reload",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/santa_driver",
|
||||
"//Source/gui:Santa",
|
||||
],
|
||||
cmd = """
|
||||
set -e
|
||||
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa/Santa.zip >/dev/null
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/gui/Santa.zip >/dev/null
|
||||
echo "You may be asked for your password for sudo"
|
||||
sudo BINARIES=/tmp/bazel_santa_reload CONF=$${BUILD_WORKSPACE_DIRECTORY}/Conf \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/Conf/install.sh
|
||||
@@ -71,29 +96,31 @@ echo "Time to stop being naughty"
|
||||
genrule(
|
||||
name = "release",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/santa_driver",
|
||||
"//Source/gui:Santa",
|
||||
"Conf/install.sh",
|
||||
"Conf/uninstall.sh",
|
||||
"Conf/com.google.santa.bundleservice.plist",
|
||||
"Conf/com.google.santa.metricservice.plist",
|
||||
"Conf/com.google.santa.syncservice.plist",
|
||||
"Conf/com.google.santad.plist",
|
||||
"Conf/com.google.santa.plist",
|
||||
"Conf/com.google.santa.asl.conf",
|
||||
"Conf/com.google.santa.newsyslog.conf",
|
||||
"Conf/Package/Makefile",
|
||||
"Conf/Package/Distribution.xml",
|
||||
"Conf/Package/notarization_tool.sh",
|
||||
"Conf/Package/package_and_sign.sh",
|
||||
"Conf/Package/postinstall",
|
||||
"Conf/Package/preinstall",
|
||||
],
|
||||
outs = ["santa-" + SANTA_VERSION + ".tar.gz"],
|
||||
outs = ["santa-release.tar.gz"],
|
||||
cmd = select({
|
||||
"//conditions:default": """
|
||||
echo "ERROR: Trying to create a release tarball without optimization."
|
||||
echo "Please add '-c opt' flag to bazel invocation"
|
||||
""",
|
||||
":opt_build": """
|
||||
# Extract santa_driver.zip and Santa.zip
|
||||
# Extract Santa.zip
|
||||
for SRC in $(SRCS); do
|
||||
if [ "$$(basename $${SRC})" == "santa_driver.zip" -o "$$(basename $${SRC})" == "Santa.zip" ]; then
|
||||
if [ "$$(basename $${SRC})" == "Santa.zip" ]; then
|
||||
mkdir -p $(@D)/binaries
|
||||
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
|
||||
fi
|
||||
@@ -101,19 +128,15 @@ genrule(
|
||||
|
||||
# Copy config files
|
||||
for SRC in $(SRCS); do
|
||||
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
|
||||
if [[ "$$(dirname $${SRC})" == *"Conf"* ]]; then
|
||||
mkdir -p $(@D)/conf
|
||||
cp $${SRC} $(@D)/conf/
|
||||
cp -H $${SRC} $(@D)/conf/
|
||||
fi
|
||||
done
|
||||
|
||||
# Gather together the dSYMs. Throw an error if no dSYMs were found
|
||||
for SRC in $(SRCS); do
|
||||
case $${SRC} in
|
||||
*santa-driver.kext.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santa-driver.kext.dSYM
|
||||
;;
|
||||
*santad.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santad.dSYM
|
||||
@@ -126,6 +149,14 @@ genrule(
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
|
||||
;;
|
||||
*santametricservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santametricservice.dSYM
|
||||
;;
|
||||
*santasyncservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santasyncservice.dSYM
|
||||
;;
|
||||
*Santa.app.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
|
||||
@@ -159,13 +190,11 @@ genrule(
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
"//Source/common:SNTFileInfoTest",
|
||||
"//Source/common:SNTPrefixTreeTest",
|
||||
"//Source/santa_driver:SantaCacheTest",
|
||||
"//Source/santactl:SNTCommandFileInfoTest",
|
||||
"//Source/santactl:SNTCommandSyncTest",
|
||||
"//Source/santad:SNTEventTableTest",
|
||||
"//Source/santad:SNTExecutionControllerTest",
|
||||
"//Source/santad:SNTRuleTableTest",
|
||||
"//Source/common:unit_tests",
|
||||
"//Source/gui:unit_tests",
|
||||
"//Source/santactl:unit_tests",
|
||||
"//Source/santad:unit_tests",
|
||||
"//Source/santametricservice:unit_tests",
|
||||
"//Source/santasyncservice:unit_tests",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
Want to contribute? Great! First, read this page (including the small print at the end).
|
||||
|
||||
### Before you contribute
|
||||
Before we can use your code, you must sign the
|
||||
[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual)
|
||||
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||
copyright to your changes, even after your contribution becomes part of our
|
||||
codebase, so we need your permission to use and distribute your code. We also
|
||||
need to be sure of various other things—for instance that you'll tell us if you
|
||||
know that your code infringes on other people's patents. You don't have to sign
|
||||
the CLA until after you've submitted your code for review and a member has
|
||||
approved it, but you must do it before we can put your code into our codebase.
|
||||
|
||||
Before you start working on a larger contribution, you should get in touch with
|
||||
us first through the [issue tracker](https://github.com/google/santa/issues)
|
||||
with your idea so that we can help out and possibly guide you. Coordinating up
|
||||
front makes it much easier to avoid frustration later on.
|
||||
|
||||
### Code reviews
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. It's also a good idea to run the
|
||||
tests beforehand, which you can do with the following commands:
|
||||
|
||||
```sh
|
||||
rake tests:logic
|
||||
rake tests:kernel # only necessary if you're changing the kext code
|
||||
```
|
||||
### Code Style
|
||||
|
||||
All code submissions should try to match the surrounding code. Wherever possible,
|
||||
code should adhere to either the
|
||||
[Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.xml)
|
||||
or the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).
|
||||
|
||||
### The small print
|
||||
Contributions made by corporations are covered by a different agreement than
|
||||
the one above, the [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
|
||||
1
CONTRIBUTING.md
Symbolic link
1
CONTRIBUTING.md
Symbolic link
@@ -0,0 +1 @@
|
||||
docs/development/contributing.md
|
||||
16
Conf/Package/Distribution.xml
Normal file
16
Conf/Package/Distribution.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>Santa</title>
|
||||
<options customize="never" allow-external-scripts="no" hostArchitectures="x86_64,arm64" />
|
||||
|
||||
<choices-outline>
|
||||
<line choice="default" />
|
||||
</choices-outline>
|
||||
|
||||
<choice id="default">
|
||||
<pkg-ref id="com.google.santa"/>
|
||||
</choice>
|
||||
|
||||
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
|
||||
|
||||
</installer-gui-script>
|
||||
@@ -1,95 +0,0 @@
|
||||
#
|
||||
# Package Makefile for Santa
|
||||
# Requires TheLuggage (github.com/unixorn/luggage) to be installed
|
||||
#
|
||||
# Will generate a package based on the latest release. You can replace
|
||||
# the PACKAGE_VERSION variable with a specific variable instead if you wish.
|
||||
#
|
||||
|
||||
LUGGAGE:=/usr/local/share/luggage/luggage.make
|
||||
include ${LUGGAGE}
|
||||
|
||||
TITLE:=santa
|
||||
REVERSE_DOMAIN:=com.google
|
||||
|
||||
# Get latest Release version using the GitHub API. Each release is bound to a
|
||||
# git tag, which should always be a semantic version number. The most recent
|
||||
# release is always first in the API result.
|
||||
PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
|
||||
python -c 'import json, sys; print json.load(sys.stdin)[0]["tag_name"]' 2>/dev/null)
|
||||
|
||||
# Get the download URL for the latest Release. Each release should have a
|
||||
# tarball named santa-$version.tar.bz2 containing all of the files associated
|
||||
# with that release. The tarball layout is:
|
||||
#
|
||||
# santa-$version.tar.bz2
|
||||
# +--santa-$version
|
||||
# |-- binaries
|
||||
# | |-- santa-driver.kext
|
||||
# | |-- Santa.app
|
||||
# |-- conf
|
||||
# | |-- install.sh
|
||||
# | |-- com.google.santad.plist
|
||||
# | |-- com.google.santagui.plist
|
||||
# | +-- com.google.santa.asl.conf
|
||||
# | +-- com.google.santa.newsyslog.conf
|
||||
# +--dsym
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
# |-- santad.dSYM
|
||||
# +-- santactl.dSYM
|
||||
PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKAGE_VERSION}/santa-${PACKAGE_VERSION}.tar.bz2"
|
||||
|
||||
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
|
||||
pack-applications-Santa.app \
|
||||
pack-Library-LaunchDaemons-com.google.santad.plist \
|
||||
pack-Library-LaunchAgents-com.google.santagui.plist \
|
||||
pack-etc-asl-com.google.santa.asl.conf \
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf \
|
||||
pack-script-preinstall \
|
||||
pack-script-postinstall
|
||||
|
||||
santa-driver.kext: download
|
||||
Santa.app: download
|
||||
com.google.santad.plist: download
|
||||
com.google.santagui.plist: download
|
||||
com.google.santa.asl.conf: download
|
||||
com.google.santa.newsyslog.conf: download
|
||||
|
||||
download:
|
||||
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
|
||||
@curl -fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
|
||||
@rm -rf *.dSYM
|
||||
|
||||
pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/asl
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/asl
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/asl
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
|
||||
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf: com.google.santa.newsyslog.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.newsyslog.conf ${WORK_D}/private/etc/newsyslog.d
|
||||
|
||||
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
|
||||
@sudo mkdir -p ${WORK_D}/Library/Extensions
|
||||
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
@sudo chown -R root:wheel ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
@sudo chmod -R 755 ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
|
||||
clean: myclean
|
||||
|
||||
myclean:
|
||||
@rm -rf *.dSYM
|
||||
@rm -rf Santa.app
|
||||
@rm -rf santa-driver.kext
|
||||
@rm -f config.plist
|
||||
@rm -f com.google.santa.asl.conf
|
||||
@rm -f com.google.santa.newsyslog.conf
|
||||
@rm -f com.google.santad.plist
|
||||
@rm -f com.google.santagui.plist
|
||||
@rm -f install.sh
|
||||
@rm -f uninstall.sh
|
||||
6
Conf/Package/notarization_tool.sh
Normal file
6
Conf/Package/notarization_tool.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Example NOTARIZATION_TOOL wrapper.
|
||||
|
||||
/usr/bin/xcrun altool --notarize-app "${2}" --primary-bundle-id "${4}" \
|
||||
-u "${NOTARIZATION_USERNAME}" -p "${NOTARIZATION_PASSWORD}"
|
||||
185
Conf/Package/package_and_sign.sh
Executable file
185
Conf/Package/package_and_sign.sh
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script signs all of Santa's components, verifies the signatures,
|
||||
# notarizes all of the components, staples them, packages them up, signs the
|
||||
# package, notarizes the package, puts the package in a DMG and notarizes the
|
||||
# DMG. It also outputs a single release tarball.
|
||||
# All of the following environment variables are required.
|
||||
|
||||
# RELEASE_ROOT is a required environment variable that points to the root
|
||||
# of an extracted release tarball produced with the :release and :release_driver
|
||||
# rules in Santa's main BUILD file.
|
||||
[[ -n "${RELEASE_ROOT}" ]] || die "RELEASE_ROOT unset"
|
||||
|
||||
# SIGNING_IDENTITY, SIGNING_TEAMID and SIGNING_KEYCHAIN are required environment
|
||||
# variables specifying the identity and keychain to pass to the codesign tool
|
||||
# and the team ID to use for verification.
|
||||
[[ -n "${SIGNING_IDENTITY}" ]] || die "SIGNING_IDENTITY unset"
|
||||
[[ -n "${SIGNING_TEAMID}" ]] || die "SIGNING_TEAMID unset"
|
||||
[[ -n "${SIGNING_KEYCHAIN}" ]] || die "SIGNING_KEYCHAIN unset"
|
||||
|
||||
# INSTALLER_SIGNING_IDENTITY and INSTALLER_SIGNING_KEYCHAIN are required
|
||||
# environment variables specifying the identity and keychain to use when signing
|
||||
# the distribution package.
|
||||
[[ -n "${INSTALLER_SIGNING_IDENTITY}" ]] || die "INSTALLER_SIGNING_IDENTITY unset"
|
||||
[[ -n "${INSTALLER_SIGNING_KEYCHAIN}" ]] || die "INSTALLER_SIGNING_KEYCHAIN unset"
|
||||
|
||||
# NOTARIZATION_TOOL is a required environment variable pointing to a wrapper
|
||||
# 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
|
||||
# place the output artifacts in.
|
||||
[[ -n "${ARTIFACTS_DIR}" ]] || die "ARTIFACTS_DIR unset"
|
||||
|
||||
################################################################################
|
||||
|
||||
function die {
|
||||
echo "${@}"
|
||||
exit 2
|
||||
}
|
||||
|
||||
readonly INPUT_APP="${RELEASE_ROOT}/binaries/Santa.app"
|
||||
readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension"
|
||||
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
|
||||
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
|
||||
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
|
||||
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
|
||||
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
|
||||
|
||||
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}"
|
||||
|
||||
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
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 \
|
||||
--options library,kill,runtime "${ARTIFACT}"
|
||||
done
|
||||
|
||||
# Notarize all the bundles
|
||||
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "zipping ${BN}"
|
||||
/usr/bin/zip -9r "${SCRATCH}/${BN}.zip" "${ARTIFACT}"
|
||||
|
||||
echo "notarizing ${BN}"
|
||||
PBID=$(/usr/bin/defaults read "${ARTIFACT}/Contents/Info.plist" CFBundleIdentifier)
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
|
||||
done
|
||||
|
||||
# Staple the App.
|
||||
for ARTIFACT in "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "stapling ${BN}"
|
||||
/usr/bin/xcrun stapler staple "${ARTIFACT}"
|
||||
done
|
||||
|
||||
# Ensure _CodeSignature/CodeResources files have 0644 permissions so they can
|
||||
# be verified without using sudo.
|
||||
/usr/bin/find "${RELEASE_ROOT}/binaries" -type f -name CodeResources -exec chmod 0644 {} \;
|
||||
/usr/bin/find "${RELEASE_ROOT}/binaries" -type d -exec chmod 0755 {} \;
|
||||
/usr/bin/find "${RELEASE_ROOT}/conf" -type f -name "com.google.santa*" -exec chmod 0644 {} \;
|
||||
|
||||
echo "verifying signatures"
|
||||
/usr/bin/codesign -vv -R="certificate leaf[subject.OU] = ${SIGNING_TEAMID}" \
|
||||
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
|
||||
|
||||
echo "creating fresh release tarball"
|
||||
/bin/mkdir -p "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/binaries" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/conf" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/dsym" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/usr/bin/tar -C "${SCRATCH}/tar_root" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
|
||||
|
||||
echo "creating app pkg"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
|
||||
"${APP_PKG_ROOT}/Library/LaunchAgents" \
|
||||
"${APP_PKG_ROOT}/Library/LaunchDaemons" \
|
||||
"${APP_PKG_ROOT}/private/etc/asl" \
|
||||
"${APP_PKG_ROOT}/private/etc/newsyslog.d"
|
||||
/bin/cp -vXR "${RELEASE_ROOT}/binaries/Santa.app" "${APP_PKG_ROOT}/Applications/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santad.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.syncservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
|
||||
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"
|
||||
/bin/cp -vXL "${SCRIPT_PATH}/postinstall" "${APP_PKG_SCRIPTS}/"
|
||||
/bin/chmod +x "${APP_PKG_SCRIPTS}/"*
|
||||
|
||||
# Disable bundle relocation.
|
||||
/usr/bin/pkgbuild --analyze --root "${APP_PKG_ROOT}" "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsRelocatable -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsVersionChecked -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleOverwriteAction -string upgrade "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace ChildBundles -json "[]" "${SCRATCH}/component.plist"
|
||||
|
||||
# Build app package
|
||||
/usr/bin/pkgbuild --identifier "com.google.santa" \
|
||||
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
|
||||
--root "${APP_PKG_ROOT}" \
|
||||
--component-plist "${SCRATCH}/component.plist" \
|
||||
--scripts "${APP_PKG_SCRIPTS}" \
|
||||
"${SCRATCH}/app.pkg"
|
||||
|
||||
# Build signed distribution package
|
||||
echo "productbuild pkg"
|
||||
/bin/mkdir -p "${SCRATCH}/${RELEASE_NAME}"
|
||||
/usr/bin/productbuild \
|
||||
--distribution "${SCRIPT_PATH}/Distribution.xml" \
|
||||
--package-path "${SCRATCH}" \
|
||||
--sign "${INSTALLER_SIGNING_IDENTITY}" --keychain "${INSTALLER_SIGNING_KEYCHAIN}" \
|
||||
"${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg"
|
||||
|
||||
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"
|
||||
|
||||
echo "stapling pkg"
|
||||
/usr/bin/xcrun stapler staple "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "failed to staple pkg"
|
||||
|
||||
echo "wrapping pkg in dmg"
|
||||
/usr/bin/hdiutil create -fs HFS+ -format UDZO \
|
||||
-volname "${RELEASE_NAME}" \
|
||||
-ov -imagekey zlib-level=9 \
|
||||
-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"
|
||||
|
||||
echo "stapling dmg"
|
||||
/usr/bin/xcrun stapler staple "${DMG_PATH}" || die "failed to staple dmg"
|
||||
@@ -13,6 +13,9 @@
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
# Remove the kext before com.google.santa.daemon loads if the SystemExtension is already present.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && rm -rf /Library/Extensions/santa-driver.kext
|
||||
|
||||
# Load com.google.santa.daemon, its main has logic to handle loading the kext
|
||||
# or relaunching itself as a SystemExtension.
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
|
||||
@@ -20,6 +23,12 @@ mkdir -p /usr/local/bin
|
||||
# Load com.google.santa.bundleservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
# Load com.google.santa.metricservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
|
||||
# Load com.google.santa.syncservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
/bin/launchctl remove com.google.santad || true
|
||||
/bin/launchctl remove com.google.santa.bundleservice || true
|
||||
/bin/launchctl remove com.google.santa.metricservice || true
|
||||
/bin/launchctl remove com.google.santa.syncservice || true
|
||||
|
||||
/bin/sleep 1
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
|
||||
> /var/db/santa/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
|
||||
? [= Sender kernel] [S= Message santa-driver:] claim
|
||||
? [= Sender kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
|
||||
? [= Facility com.google.santa] claim
|
||||
? [= Facility com.google.santa] file /var/db/santa/santa.log
|
||||
22
Conf/com.google.santa.metricservice.plist
Normal file
22
Conf/com.google.santa.metricservice.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.metricservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santametricservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.metricservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,2 +1,2 @@
|
||||
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
|
||||
/var/db/santa/santa.log root:wheel 644 10 25000 * NZ
|
||||
/var/db/santa/santa.log root:wheel 644 10 25000 * Z
|
||||
|
||||
22
Conf/com.google.santa.syncservice.plist
Normal file
22
Conf/com.google.santa.syncservice.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.syncservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santasyncservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.syncservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -24,6 +24,12 @@ fi
|
||||
# Unload bundle service
|
||||
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
|
||||
|
||||
# Unload metric service
|
||||
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
|
||||
|
||||
# Unload sync service
|
||||
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
@@ -43,20 +49,21 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
|
||||
/bin/rm -rf /Applications/Santa.app 2>&1
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
|
||||
/bin/rm /etc/asl/com.google.santa.asl.conf
|
||||
|
||||
# Copy new files.
|
||||
/bin/mkdir -p /var/db/santa
|
||||
|
||||
/bin/cp -r ${BINARIES}/Santa.app /Applications
|
||||
/bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions
|
||||
|
||||
/bin/mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
|
||||
|
||||
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
|
||||
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
|
||||
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
# Reload syslogd to pick up ASL configuration change.
|
||||
@@ -68,6 +75,12 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
# Load com.google.santa.bundleservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
# Load com.google.santa.metricservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
|
||||
# Load com.google.santa.syncservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
|
||||
|
||||
# For macOS 10.15+ this will block up to 60 seconds
|
||||
/Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && /Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
# remove helper XPC services
|
||||
/bin/launchctl remove com.google.santa.bundleservice
|
||||
/bin/launchctl remove com.google.santa.metricservice
|
||||
/bin/launchctl remove com.google.santa.syncservice
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
@@ -24,6 +28,8 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
|
||||
/bin/rm -f /usr/local/bin/santactl # just a symlink
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
#include <SantaCache.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data,
|
||||
std::size_t size) {
|
||||
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
|
||||
|
||||
std::uint64_t fields[2] = {};
|
||||
@@ -33,7 +34,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
auto returned_value = decision_cache.get(fields[0]);
|
||||
|
||||
if (returned_value != fields[1]) {
|
||||
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value << "\n";
|
||||
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value
|
||||
<< "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <SNTCommandSyncRuleDownload.h>
|
||||
#include <SNTCommandSyncState.h>
|
||||
#include <SNTCommandSyncConstants.h>
|
||||
#include <SNTRule.h>
|
||||
#include <SNTSyncConstants.h>
|
||||
#include <SNTSyncRuleDownload.h>
|
||||
#include <SNTSyncState.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
|
||||
@@ -41,12 +41,12 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTCommandSyncState *state = [[SNTCommandSyncState alloc] init];
|
||||
SNTSyncState *state = [[SNTSyncState alloc] init];
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTCommandSyncRuleDownload *obj = [[SNTCommandSyncRuleDownload alloc] initWithState:state];
|
||||
SNTSyncRuleDownload *obj = [[SNTSyncRuleDownload alloc] initWithState:state];
|
||||
if (!obj) {
|
||||
return 0;
|
||||
}
|
||||
@@ -57,6 +57,6 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@@ -23,15 +23,14 @@
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > 16) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= 16)" << std::endl;
|
||||
std::cerr << "Invalid buffer size of " << size << " (should be <= 16)" << std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
santa_vnode_id_t vnodeID = {};
|
||||
std::memcpy(&vnodeID, data, size);
|
||||
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
@@ -40,16 +39,20 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;;
|
||||
}
|
||||
}];
|
||||
|
||||
[[daemonConn remoteObjectProxy]
|
||||
checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;
|
||||
;
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@@ -34,9 +34,8 @@ struct InputData {
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > sizeof(InputData)) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= " << sizeof(InputData)
|
||||
<< ")" << std::endl;
|
||||
std::cerr << "Invalid buffer size of " << size << " (should be <= " << sizeof(InputData) << ")"
|
||||
<< std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -45,11 +44,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
std::memcpy(&input_data, data, size);
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.state = (SNTRuleState) input_data.state;
|
||||
newRule.type = (SNTRuleType) input_data.type;
|
||||
newRule.shasum = @(input_data.hash);
|
||||
newRule.state = (SNTRuleState)input_data.state;
|
||||
newRule.type = (SNTRuleType)input_data.type;
|
||||
newRule.identifier = @(input_data.hash);
|
||||
newRule.customMsg = @"";
|
||||
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
@@ -57,17 +56,18 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[[daemonConn remoteObjectProxy]
|
||||
databaseRuleAddRules:@[ newRule ]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
22
Podfile
22
Podfile
@@ -1,22 +0,0 @@
|
||||
|
||||
def common_pods
|
||||
pod 'MOLXPCConnection'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'FMDB'
|
||||
pod 'MOLCertificate'
|
||||
pod 'OCMock'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLFCMClient'
|
||||
end
|
||||
|
||||
project './Santa.xcodeproj'
|
||||
|
||||
project = Xcodeproj::Project.open "./Santa.xcodeproj"
|
||||
project.targets.each do |t|
|
||||
if t.name == "santa-driver"
|
||||
next
|
||||
end
|
||||
target t.name do
|
||||
common_pods
|
||||
end
|
||||
end
|
||||
46
Podfile.lock
46
Podfile.lock
@@ -1,46 +0,0 @@
|
||||
PODS:
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- MOLAuthenticatingURLSession (2.4):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLCertificate (1.9)
|
||||
- MOLCodesignChecker (1.10):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLFCMClient (1.8):
|
||||
- MOLAuthenticatingURLSession (~> 2.4)
|
||||
- MOLXPCConnection (1.2):
|
||||
- MOLCodesignChecker (~> 1.9)
|
||||
- OCMock (3.5)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- MOLFCMClient
|
||||
- MOLXPCConnection
|
||||
- OCMock
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- MOLFCMClient
|
||||
- MOLXPCConnection
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
|
||||
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
|
||||
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
|
||||
MOLFCMClient: 2bfbacd45cc11e1ca3c077e97b80401c4e4a54f1
|
||||
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
|
||||
OCMock: 4ab4577fc941af31f4a0398f6e7e230cf21fc72a
|
||||
|
||||
PODFILE CHECKSUM: d03767a9915896232523962c98d9ff7294aec2b7
|
||||
|
||||
COCOAPODS: 1.7.5
|
||||
129
README.md
129
README.md
@@ -1,29 +1,22 @@
|
||||
# Santa [![Build Status][build-status-img]][build-status-link] [![Documentation Status][doc-status-img]][doc-status-link]
|
||||
|
||||
[build-status-img]: https://travis-ci.org/google/santa.png?branch=master
|
||||
[build-status-link]: https://travis-ci.org/google/santa
|
||||
[doc-status-img]: https://readthedocs.org/projects/santa/badge/?version=latest
|
||||
[doc-status-link]: https://santa.readthedocs.io/en/latest/?badge=latest
|
||||
# Santa [](https://github.com/google/santa/actions/workflows/ci.yml)
|
||||
|
||||
<p align="center">
|
||||
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
<img src="https://raw.githubusercontent.com/google/santa/main/Source/gui/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</p>
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for macOS. It consists of a
|
||||
kernel extension (or a system extension on macOS 10.15+) that monitors for executions, a userland daemon that makes
|
||||
execution decisions based on the contents of a SQLite database, a GUI agent
|
||||
that notifies the user in case of a block decision and a command-line utility
|
||||
for managing the system and synchronizing the database with a server.
|
||||
Santa is a binary 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
|
||||
synchronizing the database with a server.
|
||||
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
Santa is a project of Google's Macintosh Operations Team.
|
||||
|
||||
# Docs
|
||||
|
||||
The Santa docs are stored in the
|
||||
[Docs](https://github.com/google/santa/blob/master/docs) directory. A Read the
|
||||
Docs instance is available here: https://santa.readthedocs.io.
|
||||
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
|
||||
at https://santa.dev.
|
||||
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
instructions for developing Santa itself.
|
||||
@@ -35,29 +28,32 @@ the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
|
||||
great place.
|
||||
|
||||
If you believe you have a bug, feel free to report [an
|
||||
issue](https://github.com/google/santa/isues) and we'll respond as soon as we
|
||||
issue](https://github.com/google/santa/issues) and we'll respond as soon as we
|
||||
can.
|
||||
|
||||
If you believe you've found a vulnerability, please read the
|
||||
[security policy](https://github.com/google/santa/security/policy) for
|
||||
disclosure reporting.
|
||||
|
||||
# Admin-Related Features
|
||||
# Features
|
||||
|
||||
* Multiple modes: In the default MONITOR mode, all binaries except those marked
|
||||
as blacklisted will be allowed to run, whilst being logged and recorded in
|
||||
the events database. In LOCKDOWN mode, only whitelisted binaries are allowed
|
||||
to run.
|
||||
as blocked will be allowed to run, whilst being logged and recorded in
|
||||
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.
|
||||
|
||||
* Certificate-based rules, with override levels: Instead of relying on a
|
||||
binary's hash (or 'fingerprint'), executables can be whitelisted/blacklisted
|
||||
by their signing certificate. You can therefore trust/block all binaries by a
|
||||
binary's hash (or 'fingerprint'), executables can be allowed/blocked by their
|
||||
signing certificate. You can therefore allow/block all binaries by a
|
||||
given publisher that were signed with that cert across version updates. A
|
||||
binary can only be whitelisted by its certificate if its signature validates
|
||||
correctly, but a rule for a binary's fingerprint will override a decision for
|
||||
a certificate; i.e. you can whitelist a certificate while blacklisting a
|
||||
binary signed with that certificate, or vice-versa.
|
||||
binary can only be allowed by its certificate if its signature validates
|
||||
correctly but a rule for a binary's fingerprint will override a decision for
|
||||
a certificate; i.e. you can allowlist a certificate while blocking a binary
|
||||
signed with that certificate, or vice-versa.
|
||||
|
||||
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature
|
||||
to that found in Managed Client (the precursor to configuration profiles,
|
||||
@@ -70,10 +66,18 @@ can.
|
||||
* Failsafe cert rules: You cannot put in a deny rule that would block the
|
||||
certificate used to sign launchd, a.k.a. pid 1, and therefore all components
|
||||
used in macOS. The binaries in every OS update (and in some cases entire new
|
||||
versions) are therefore auto-whitelisted. This does not affect binaries from
|
||||
Apple's App Store, which use various certs that change regularly for common
|
||||
apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct
|
||||
separate cert than other Google apps.
|
||||
versions) are therefore automatically allowed. This does not affect binaries
|
||||
from Apple's App Store, which use various certs that change regularly for
|
||||
common apps. Likewise, you cannot block Santa itself, and Santa uses a
|
||||
distinct separate cert than other Google apps.
|
||||
|
||||
* Userland components validate each other: each of the userland components (the
|
||||
daemon, the GUI agent and the command-line utility) communicate with each
|
||||
other using XPC and check that their signing certificates are identical
|
||||
before any communication is accepted.
|
||||
|
||||
* Caching: allowed binaries are cached so the processing required to make a
|
||||
request is only done if the binary isn't already cached.
|
||||
|
||||
# Intentions and Expectations
|
||||
|
||||
@@ -90,42 +94,17 @@ protect hosts in whatever other ways you see fit.
|
||||
|
||||
# Security and Performance-Related Features
|
||||
|
||||
* In-kernel caching: whitelisted binaries are cached in the kernel so the
|
||||
processing required to make a request is only done if the binary isn't
|
||||
already cached.
|
||||
|
||||
* Userland components validate each other: each of the userland components (the
|
||||
daemon, the GUI agent and the command-line utility) communicate with each
|
||||
other using XPC and check that their signing certificates are identical
|
||||
before any communication is accepted.
|
||||
|
||||
* Kext uses only KPIs: the kernel extension only uses provided kernel
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
|
||||
# Known Issues
|
||||
|
||||
* Santa only blocks execution (execve and variants), it doesn't protect against
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been
|
||||
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version
|
||||
0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were
|
||||
exploited in some versions of macOS. We are working on also protecting
|
||||
against similar avenues of attack.
|
||||
|
||||
* Kext communication security: the kext will only accept a connection from a
|
||||
single client at a time and said client must be running as root. We haven't
|
||||
yet found a good way to ensure the kext only accepts connections from a valid
|
||||
client.
|
||||
|
||||
* Database protection: the SQLite database is installed with permissions so
|
||||
that only the root user can read/write it. We're considering approaches to
|
||||
secure this further.
|
||||
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`.
|
||||
|
||||
* Scripts: Santa is currently written to ignore any execution that isn't a
|
||||
binary. This is because after weighing the administration cost vs the
|
||||
benefit, we found it wasn't worthwhile. Additionally, a number of
|
||||
applications make use of temporary generated scripts, which we can't possibly
|
||||
whitelist and not doing so would cause problems. We're happy to revisit this
|
||||
allowlist and not doing so would cause problems. We're happy to revisit this
|
||||
(or at least make it an option) if it would be useful to others.
|
||||
|
||||
# Sync Servers
|
||||
@@ -134,13 +113,17 @@ protect hosts in whatever other ways you see fit.
|
||||
management server, which uploads events that have occurred on the machine and
|
||||
downloads new rules. There are several open-source servers you can sync with:
|
||||
|
||||
* [Upvote](https://github.com/google/upvote) - An AppEngine-based server
|
||||
that implements social voting to make managing a large fleet easier.
|
||||
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
|
||||
serves hardcoded rules from simple configuration files.
|
||||
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
|
||||
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
|
||||
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
|
||||
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
|
||||
centralized service that pulls data from multiple sources and deploy
|
||||
configurations to multiple services.
|
||||
* [Zercurity](https://github.com/zercurity/zercurity) - A dockerized service
|
||||
for managing and monitoring applications across a large fleet utilizing
|
||||
Santa + Osquery.
|
||||
|
||||
* Alternatively, `santactl` can configure rules locally (without a sync
|
||||
server).
|
||||
@@ -150,34 +133,12 @@ protect hosts in whatever other ways you see fit.
|
||||
A tool like Santa doesn't really lend itself to screenshots, so here's a video
|
||||
instead.
|
||||
|
||||
<p align="center"> <img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif"
|
||||
alt="Santa Block Video" /> </p>
|
||||
|
||||
# Kext Signing
|
||||
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
|
||||
Developer ID certificate with a kernel extension flag. Without it, the only way
|
||||
to load an extension is to enable kext-dev-mode or disable SIP, depending on
|
||||
the OS version.
|
||||
|
||||
There are two possible solutions for this, for distribution purposes:
|
||||
|
||||
1) Use a [pre-built, pre-signed
|
||||
version](https://github.com/google/santa/releases) of the kext that we supply.
|
||||
Each time changes are made to the kext code we will update the pre-built
|
||||
version that you can make use of. This doesn't prevent you from making changes
|
||||
to the non-kext parts of Santa and distributing those. If you make changes to
|
||||
the kext and make a pull request, we can merge them in and distribute a new
|
||||
version of the pre-signed kext.
|
||||
|
||||
2) Apply for your own [kext signing
|
||||
certificate](https://developer.apple.com/contact/kext/). Apple will only grant
|
||||
this for broad distribution within an organization, they won't issue them just
|
||||
for testing purposes.
|
||||
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
|
||||
|
||||
# Contributing
|
||||
Patches to this project are very much welcome. Please see the
|
||||
[CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
|
||||
file.
|
||||
[CONTRIBUTING](https://santa.dev/development/contributing) doc.
|
||||
|
||||
# Disclaimer
|
||||
This is **not** an official Google product.
|
||||
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 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
|
||||
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:
|
||||
|
||||
`gpg --keyserver pool.sks-keyservers.net --recv-key 0x92AFE41DAB49BBB6`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,79 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
debugAsWhichUser = "root"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
10
Santa.xcworkspace/contents.xcworkspacedata
generated
10
Santa.xcworkspace/contents.xcworkspacedata
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Santa.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,8 +1,50 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
proto_library(
|
||||
name = "santa_proto",
|
||||
srcs = ["santa.proto"],
|
||||
deps = [
|
||||
"@com_google_protobuf//:any_proto",
|
||||
"@com_google_protobuf//:timestamp_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "santa_cc_proto",
|
||||
deps = [":santa_proto"],
|
||||
)
|
||||
|
||||
# Note: Simple wrapper for a `cc_proto_library` target which cannot be directly
|
||||
# depended upon by an `objc_library` target.
|
||||
cc_library(
|
||||
name = "santa_cc_proto_library_wrapper",
|
||||
hdrs = ["santa_proto_include_wrapper.h"],
|
||||
deps = [
|
||||
":santa_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SantaCacheTest",
|
||||
srcs = [
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage",
|
||||
@@ -10,7 +52,9 @@ objc_library(
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -21,7 +65,10 @@ objc_library(
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTDeviceEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -30,14 +77,23 @@ objc_library(
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
textual_hdrs = ["SNTCommonEnums.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -46,12 +102,29 @@ objc_library(
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTLogging",
|
||||
":SNTRule",
|
||||
":SNTStrengthify",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTKVOManager",
|
||||
srcs = ["SNTKVOManager.mm"],
|
||||
hdrs = ["SNTKVOManager.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTKVOManagerTest",
|
||||
srcs = ["SNTKVOManagerTest.mm"],
|
||||
deps = [
|
||||
":SNTKVOManager",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDropRootPrivs",
|
||||
srcs = ["SNTDropRootPrivs.m"],
|
||||
@@ -63,25 +136,26 @@ objc_library(
|
||||
srcs = ["SNTFileInfo.m"],
|
||||
hdrs = ["SNTFileInfo.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTKernelCommon",
|
||||
hdrs = ["SNTKernelCommon.h"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTLoggingKernel",
|
||||
hdrs = ["SNTLogging.h"],
|
||||
name = "SNTCommon",
|
||||
hdrs = ["SNTCommon.h"],
|
||||
defines = [
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
hdrs = ["SNTLogging.h"],
|
||||
deps = [":SNTConfigurator"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
@@ -92,24 +166,20 @@ cc_library(
|
||||
deps = [":SNTLogging"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTPrefixTreeKernel",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = [
|
||||
"-std=c++11",
|
||||
"-mkernel",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = ["KERNEL"],
|
||||
deps = [":SNTLoggingKernel"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
deps = [":SNTCommonEnums"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSyncConstants",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTRuleTest",
|
||||
srcs = ["SNTRuleTest.m"],
|
||||
deps = [":SNTRule"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -127,6 +197,12 @@ cc_library(
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSyncConstants",
|
||||
srcs = ["SNTSyncConstants.m"],
|
||||
hdrs = ["SNTSyncConstants.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSystemInfo",
|
||||
srcs = ["SNTSystemInfo.m"],
|
||||
@@ -144,14 +220,30 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCMetricServiceInterface",
|
||||
srcs = ["SNTXPCMetricServiceInterface.m"],
|
||||
hdrs = ["SNTXPCMetricServiceInterface.h"],
|
||||
deps = [
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCControlInterface",
|
||||
srcs = ["SNTXPCControlInterface.m"],
|
||||
hdrs = ["SNTXPCControlInterface.h"],
|
||||
defines = select({
|
||||
"//:adhoc_build": ["SANTAADHOC"],
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTConfigurator",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCUnprivilegedControlInterface",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
@@ -167,12 +259,20 @@ objc_library(
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncdInterface",
|
||||
srcs = ["SNTXPCSyncdInterface.m"],
|
||||
hdrs = ["SNTXPCSyncdInterface.h"],
|
||||
name = "SNTMetricSet",
|
||||
srcs = ["SNTMetricSet.m"],
|
||||
hdrs = ["SNTMetricSet.h"],
|
||||
deps = [":SNTCommonEnums"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncServiceInterface",
|
||||
srcs = ["SNTXPCSyncServiceInterface.m"],
|
||||
hdrs = ["SNTXPCSyncServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -181,8 +281,8 @@ objc_library(
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
@@ -195,6 +295,7 @@ santa_unit_test(
|
||||
name = "SNTFileInfoTest",
|
||||
srcs = ["SNTFileInfoTest.m"],
|
||||
resources = [
|
||||
"testdata/32bitplist",
|
||||
"testdata/bad_pagezero",
|
||||
"testdata/missing_pagezero",
|
||||
],
|
||||
@@ -208,5 +309,49 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTPrefixTreeTest",
|
||||
srcs = ["SNTPrefixTreeTest.mm"],
|
||||
deps = ["SNTPrefixTree"],
|
||||
deps = [":SNTPrefixTree"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTMetricSetTest",
|
||||
srcs = ["SNTMetricSetTest.m"],
|
||||
deps = [":SNTMetricSet"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTCachedDecisionTest",
|
||||
srcs = ["SNTCachedDecisionTest.mm"],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":SNTCachedDecisionTest",
|
||||
":SNTFileInfoTest",
|
||||
":SNTKVOManagerTest",
|
||||
":SNTMetricSetTest",
|
||||
":SNTPrefixTreeTest",
|
||||
":SNTRuleTest",
|
||||
":SantaCacheTest",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "TestUtils",
|
||||
testonly = 1,
|
||||
srcs = ["TestUtils.mm"],
|
||||
hdrs = ["TestUtils.h"],
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -24,12 +24,17 @@
|
||||
|
||||
///
|
||||
/// Return a message suitable for presenting to the user.
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one.
|
||||
///
|
||||
/// In SantaGUI this will return an NSAttributedString with links and formatting included
|
||||
/// while for santad all HTML will be properly stripped.
|
||||
///
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message;
|
||||
|
||||
///
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one, formatted using
|
||||
/// +[SNTBlockMessage formatMessage].
|
||||
///
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage;
|
||||
|
||||
|
||||
@@ -17,35 +17,52 @@
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *htmlHeader = @"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: %@;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message {
|
||||
NSString *htmlHeader =
|
||||
@"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: %@;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
|
||||
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
|
||||
@"@media (prefers-color-scheme: dark) {"
|
||||
@" body {"
|
||||
@" color: #ddd;"
|
||||
@" }"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
|
||||
@"@media (prefers-color-scheme: dark) {"
|
||||
@" body {"
|
||||
@" color: #ddd;"
|
||||
@" }"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
|
||||
// Support Dark Mode. Note, the returned NSAttributedString is static and does not update when
|
||||
// the OS switches modes.
|
||||
NSString *mode = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"];
|
||||
BOOL dark = [mode isEqualToString:@"Dark"];
|
||||
BOOL dark = [mode isEqualToString:@"Dark"];
|
||||
htmlHeader = [NSString stringWithFormat:htmlHeader, dark ? @"#ddd" : @"#333"];
|
||||
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *message;
|
||||
if (customMessage.length) {
|
||||
message = customMessage;
|
||||
@@ -62,19 +79,7 @@
|
||||
@"because it has been deemed malicious.";
|
||||
}
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
return [SNTBlockMessage formatMessage:message];
|
||||
}
|
||||
|
||||
+ (NSString *)stringFromHTML:(NSString *)html {
|
||||
@@ -89,13 +94,14 @@
|
||||
|
||||
// Strip any HTML tags out of the message. Also remove any content inside <style> tags and
|
||||
// replace <br> elements with a newline.
|
||||
NSString *stripXslt = @"<?xml version='1.0' encoding='utf-8'?>"
|
||||
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
|
||||
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
|
||||
@"<xsl:output method='text'/>"
|
||||
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
|
||||
@"<xsl:template match='style'/>"
|
||||
@"</xsl:stylesheet>";
|
||||
NSString *stripXslt =
|
||||
@"<?xml version='1.0' encoding='utf-8'?>"
|
||||
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
|
||||
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
|
||||
@"<xsl:output method='text'/>"
|
||||
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
|
||||
@"<xsl:template match='style'/>"
|
||||
@"</xsl:stylesheet>";
|
||||
NSData *data = [xml objectByApplyingXSLTString:stripXslt arguments:NULL error:&error];
|
||||
if (error || ![data isKindOfClass:[NSData class]]) {
|
||||
return html;
|
||||
@@ -106,13 +112,23 @@
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
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 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%"
|
||||
@@ -122,6 +138,15 @@
|
||||
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];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -12,10 +12,11 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
|
||||
@class MOLCertificate;
|
||||
|
||||
@@ -24,6 +25,8 @@
|
||||
///
|
||||
@interface SNTCachedDecision : NSObject
|
||||
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile;
|
||||
|
||||
@property santa_vnode_id_t vnodeId;
|
||||
@property SNTEventState decision;
|
||||
@property NSString *decisionExtra;
|
||||
@@ -32,6 +35,7 @@
|
||||
@property NSString *certSHA256;
|
||||
@property NSString *certCommonName;
|
||||
@property NSArray<MOLCertificate *> *certChain;
|
||||
@property NSString *teamID;
|
||||
|
||||
@property NSString *quarantineURL;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -15,4 +15,14 @@
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
|
||||
@implementation SNTCachedDecision
|
||||
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_vnodeId.fsid = (uint64_t)esFile->stat.st_dev;
|
||||
_vnodeId.fileid = esFile->stat.st_ino;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
36
Source/common/SNTCachedDecisionTest.mm
Normal file
36
Source/common/SNTCachedDecisionTest.mm
Normal file
@@ -0,0 +1,36 @@
|
||||
/// Copyright 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.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#include "Source/common/TestUtils.h"
|
||||
|
||||
@interface SNTCachedDecisionTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTCachedDecisionTest
|
||||
|
||||
- (void)testSNTCachedDecisionInit {
|
||||
// Ensure the vnodeId field is properly set from the es_file_t
|
||||
struct stat sb = MakeStat();
|
||||
es_file_t file = MakeESFile("foo", sb);
|
||||
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] initWithEndpointSecurityFile:&file];
|
||||
|
||||
XCTAssertEqual(sb.st_ino, cd.vnodeId.fileid);
|
||||
XCTAssertEqual(sb.st_dev, cd.vnodeId.fsid);
|
||||
}
|
||||
|
||||
@end
|
||||
59
Source/common/SNTCommon.h
Normal file
59
Source/common/SNTCommon.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/// 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
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -24,18 +24,19 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
|
||||
SNTRuleTypeBinary = 1,
|
||||
SNTRuleTypeCertificate = 2,
|
||||
SNTRuleTypeTeamID = 3,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
SNTRuleStateUnknown,
|
||||
|
||||
SNTRuleStateWhitelist = 1,
|
||||
SNTRuleStateBlacklist = 2,
|
||||
SNTRuleStateSilentBlacklist = 3,
|
||||
SNTRuleStateAllow = 1,
|
||||
SNTRuleStateBlock = 2,
|
||||
SNTRuleStateSilentBlock = 3,
|
||||
SNTRuleStateRemove = 4,
|
||||
|
||||
SNTRuleStateWhitelistCompiler = 5,
|
||||
SNTRuleStateWhitelistTransitive = 6,
|
||||
SNTRuleStateAllowCompiler = 5,
|
||||
SNTRuleStateAllowTransitive = 6,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
@@ -55,6 +56,8 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
SNTEventStateBlockBinary = 1 << 17,
|
||||
SNTEventStateBlockCertificate = 1 << 18,
|
||||
SNTEventStateBlockScope = 1 << 19,
|
||||
SNTEventStateBlockTeamID = 1 << 20,
|
||||
SNTEventStateBlockLongPath = 1 << 21,
|
||||
|
||||
// Bits 24-31 store allow decision types
|
||||
SNTEventStateAllowUnknown = 1 << 24,
|
||||
@@ -64,6 +67,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
SNTEventStateAllowCompiler = 1 << 28,
|
||||
SNTEventStateAllowTransitive = 1 << 29,
|
||||
SNTEventStateAllowPendingTransitive = 1 << 30,
|
||||
SNTEventStateAllowTeamID = 1 << 31,
|
||||
|
||||
// Block and Allow masks
|
||||
SNTEventStateBlock = 0xFF << 16,
|
||||
@@ -89,9 +93,32 @@ typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
|
||||
typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath = "/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
static const char *kSantaCtlPath = "/Applications/Santa.app/Contents/MacOS/santactl";
|
||||
// The return status of a sync.
|
||||
typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
|
||||
SNTSyncStatusTypeSuccess,
|
||||
SNTSyncStatusTypePreflightFailed,
|
||||
SNTSyncStatusTypeEventUploadFailed,
|
||||
SNTSyncStatusTypeRuleDownloadFailed,
|
||||
SNTSyncStatusTypePostflightFailed,
|
||||
SNTSyncStatusTypeTooManySyncsInProgress,
|
||||
SNTSyncStatusTypeMissingSyncBaseURL,
|
||||
SNTSyncStatusTypeMissingMachineID,
|
||||
SNTSyncStatusTypeDaemonTimeout,
|
||||
SNTSyncStatusTypeSyncStarted,
|
||||
SNTSyncStatusTypeUnknown,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
SNTMetricFormatTypeUnknown,
|
||||
SNTMetricFormatTypeRawJSON,
|
||||
SNTMetricFormatTypeMonarchJSON,
|
||||
};
|
||||
|
||||
static const char *kSantaDPath =
|
||||
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
|
||||
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
static const char *kSantaAppPath = "/Applications/Santa.app";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
|
||||
///
|
||||
/// Singleton that provides an interface for managing configuration values on disk
|
||||
/// @note This class is designed as a singleton but that is not strictly enforced.
|
||||
@@ -26,7 +28,7 @@
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// The operating mode.
|
||||
/// The operating mode. Defaults to MONITOR.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTClientMode clientMode;
|
||||
|
||||
@@ -36,32 +38,70 @@
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
|
||||
|
||||
///
|
||||
/// The regex of whitelisted paths. Regexes are specified in ICU format.
|
||||
/// 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
|
||||
/// 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
|
||||
/// potential for causing problems.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL failClosed;
|
||||
|
||||
///
|
||||
/// A set of static rules that should always apply. These can be used as a
|
||||
/// fallback set of rules for management tools that should always be allowed to
|
||||
/// run even if a sync server does something unexpected. It can also be used
|
||||
/// as the sole source of rules, distributed with an MDM.
|
||||
///
|
||||
/// The value of this key should be an array containing dictionaries. Each
|
||||
/// dictionary should contain the same keys used for syncing, e.g:
|
||||
///
|
||||
/// <key>StaticRules</key>
|
||||
/// <array>
|
||||
/// <dict>
|
||||
/// <key>identifier</key>
|
||||
/// <string>binary sha256, certificate sha256, team ID</string>
|
||||
/// <key>rule_type</key>
|
||||
/// <string>BINARY</string> (one of BINARY, CERTIFICATE or TEAMID)
|
||||
/// <key>policy</key>
|
||||
/// <string>BLOCKLIST</string> (one of ALLOWLIST, ALLOWLIST_COMPILER, BLOCKLIST,
|
||||
/// SILENT_BLOCKLIST)
|
||||
/// </dict>
|
||||
/// </array>
|
||||
///
|
||||
/// The return of this property is a dictionary where the keys are the
|
||||
/// identifiers of each rule, with the SNTRule as a value
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary<NSString *, SNTRule *> *staticRules;
|
||||
|
||||
///
|
||||
/// The regex of allowed paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *whitelistPathRegex;
|
||||
@property(readonly, nonatomic) NSRegularExpression *allowedPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of whitelisted paths as received from a sync server.
|
||||
/// Set the regex of allowed paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re;
|
||||
- (void)setSyncServerAllowedPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of blacklisted paths. Regexes are specified in ICU format.
|
||||
/// The regex of blocked paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *blacklistPathRegex;
|
||||
@property(readonly, nonatomic) NSRegularExpression *blockedPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of blacklisted paths as received from a sync server.
|
||||
/// Set the regex of blocked paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re;
|
||||
- (void)setSyncServerBlockedPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
|
||||
@@ -133,15 +173,20 @@
|
||||
///
|
||||
/// Enable bad signature protection, defaults to NO.
|
||||
/// When enabled, a binary that is signed but has a bad signature (cert revoked, binary
|
||||
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary whitelist
|
||||
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary allowlist
|
||||
/// rule exists.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableBadSignatureProtection;
|
||||
|
||||
///
|
||||
/// Defines how event logs are stored. Options are:
|
||||
/// SNTEventLogTypeSyslog: Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog: Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// SNTEventLogTypeSyslog "syslog": Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog "file": Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// SNTEventLogTypeNull "null": Logs nothing
|
||||
/// SNTEventLogTypeProtobuf "protobuf": (BETA) Sent to a file on disk, using a maildir-like
|
||||
/// format. Use spoolDirectory to specify a path. Use spoolDirectoryFileSizeThresholdKB,
|
||||
/// spoolDirectorySizeThresholdMB and spoolDirectoryEventMaxFlushTimeSec to configure
|
||||
/// additional settings.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
@@ -157,6 +202,42 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectory will provide the base path used for
|
||||
/// saving logs using a maildir-like format.
|
||||
/// Defaults to /var/db/santa/spool.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *spoolDirectory;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the spoolDirectory.
|
||||
/// Defaults to 250.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectoryFileSizeThresholdKB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the spoolDirectory.
|
||||
/// Defaults to 100.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// of time an event will be stored in memory before being written to disk.
|
||||
/// Defaults to 15.0.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) float spoolDirectoryEventMaxFlushTimeSec;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
@@ -164,17 +245,24 @@
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
|
||||
///
|
||||
/// Use the bundled SystemExtension on macOS 10.15+, defaults to YES.
|
||||
/// Disable to continue using the bundled KEXT.
|
||||
/// This is a one way switch, if this is ever true on macOS 10.15+ the KEXT will be deleted.
|
||||
/// This gives admins control over the timing of switching to the SystemExtension. The intended use case is to have an MDM deliver
|
||||
/// the requisite SystemExtension and TCC profiles before attempting to load.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSystemExtension;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
///
|
||||
/// When silent mode is enabled, Santa will never show notifications for
|
||||
/// blocked processes.
|
||||
///
|
||||
/// This can be a very confusing experience for users, use with caution.
|
||||
///
|
||||
/// Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSilentMode;
|
||||
|
||||
///
|
||||
/// The text to display when opening Santa.app.
|
||||
/// If unset, the default text will be displayed.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *aboutText;
|
||||
|
||||
///
|
||||
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
|
||||
/// If unset, the button will not be displayed.
|
||||
@@ -191,6 +279,9 @@
|
||||
/// %file_sha% -- SHA-256 of the file that was blocked.
|
||||
/// %machine_id% -- ID of the machine.
|
||||
/// %username% -- executing user.
|
||||
/// %serial% -- System's serial number.
|
||||
/// %uuid% -- System's UUID.
|
||||
/// %hostname% -- System's full hostname.
|
||||
///
|
||||
/// @note: This is not an NSURL because the format-string parsing is done elsewhere.
|
||||
///
|
||||
@@ -216,6 +307,20 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is denied
|
||||
/// from the BlockUSB configuration setting. If not configured, a reasonable
|
||||
/// default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is forcibly
|
||||
/// remounted to a different set of permissions from the BlockUSB and RemountUSBMode
|
||||
/// configuration settings. If not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *remountUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// The notification text to display when the client goes into MONITOR mode.
|
||||
/// Defaults to "Switching into Monitor mode"
|
||||
@@ -235,6 +340,14 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *syncBaseURL;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
|
||||
|
||||
///
|
||||
/// The machine owner.
|
||||
///
|
||||
@@ -255,6 +368,17 @@
|
||||
///
|
||||
@property(nonatomic) BOOL syncCleanRequired;
|
||||
|
||||
///
|
||||
/// USB Mount Blocking. Defaults to false.
|
||||
///
|
||||
@property(nonatomic) BOOL blockUSBMount;
|
||||
|
||||
///
|
||||
/// Comma-seperated `$ 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, this over-rides the default machine ID used for syncing.
|
||||
///
|
||||
@@ -266,14 +390,14 @@
|
||||
///
|
||||
@property BOOL enableBundles;
|
||||
|
||||
#pragma mark Transitive Whitelisting Settings
|
||||
#pragma mark Transitive Allowlist Settings
|
||||
|
||||
///
|
||||
/// If YES, binaries marked with SNTRuleStateWhitelistCompiler rules are allowed to transitively
|
||||
/// whitelist any executables that they produce. If NO, SNTRuleStateWhitelistCompiler rules are
|
||||
/// interpreted as if they were simply SNTRuleStateWhitelist rules. Defaults to NO.
|
||||
/// If YES, binaries marked with SNTRuleStateAllowCompiler rules are allowed to transitively
|
||||
/// allow any executables that they produce. If NO, SNTRuleStateAllowCompiler rules are
|
||||
/// interpreted as if they were simply SNTRuleStateAllow rules. Defaults to NO.
|
||||
///
|
||||
@property BOOL enableTransitiveWhitelisting;
|
||||
@property BOOL enableTransitiveRules;
|
||||
|
||||
#pragma mark Server Auth Settings
|
||||
|
||||
@@ -312,6 +436,98 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *syncClientAuthCertificateIssuer;
|
||||
|
||||
///
|
||||
/// If true, syncs will upload events when a clean sync is requested. Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableCleanSyncEventUpload;
|
||||
|
||||
///
|
||||
/// If true, events will be uploaded for all executions, even those that are allowed.
|
||||
/// Use with caution, this generates a lot of events. Defaults to false.
|
||||
///
|
||||
@property(nonatomic) BOOL enableAllEventUpload;
|
||||
|
||||
///
|
||||
/// If true, events will *not* be uploaded for ALLOW_UNKNOWN events for clients in Monitor mode.
|
||||
///
|
||||
@property(nonatomic) BOOL disableUnknownEventUpload;
|
||||
|
||||
///
|
||||
/// If true, forks and exits will be logged. Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableForkAndExitLogging;
|
||||
|
||||
///
|
||||
/// If true, ignore actions from other endpoint security clients. Defaults to false. This only
|
||||
/// applies when running as a sysx.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL ignoreOtherEndpointSecurityClients;
|
||||
|
||||
///
|
||||
/// If true, debug logging will be enabled for all Santa components. Defaults to false.
|
||||
/// Passing --debug as an executable argument will enable debug logging for that specific
|
||||
/// component.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableDebugLogging;
|
||||
|
||||
///
|
||||
/// If true, compressed requests from "santactl sync" will set "Content-Encoding" to "zlib"
|
||||
/// instead of the new default "deflate". If syncing with Upvote deployed at commit 0b4477d
|
||||
/// or below, set this option to true.
|
||||
/// Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableBackwardsCompatibleContentEncoding;
|
||||
|
||||
///
|
||||
/// Contains the FCM project name.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fcmProject;
|
||||
|
||||
///
|
||||
/// Contains the FCM project entity.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fcmEntity;
|
||||
|
||||
///
|
||||
/// Contains the FCM project API key.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fcmAPIKey;
|
||||
|
||||
///
|
||||
/// True if fcmProject, fcmEntity and fcmAPIKey are all set. Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL fcmEnabled;
|
||||
|
||||
///
|
||||
/// True if metricsFormat and metricsURL are set. False otherwise.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL exportMetrics;
|
||||
|
||||
///
|
||||
/// Format to export Metrics as.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTMetricFormatType metricFormat;
|
||||
|
||||
///
|
||||
/// URL describing where metrics are exported, defaults to nil.
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *metricURL;
|
||||
|
||||
///
|
||||
/// Extra Metric Labels to add to the metrics payloads.
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *extraMetricLabels;
|
||||
|
||||
///
|
||||
/// Duration in seconds of how often the metrics should be exported.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportInterval;
|
||||
|
||||
///
|
||||
/// Duration in seconds for metrics export timeout. Defaults to 30;
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportTimeout;
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2014-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.
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@@ -24,13 +24,20 @@
|
||||
/// A NSUserDefaults object set to use the com.google.santa suite.
|
||||
@property(readonly, nonatomic) NSUserDefaults *defaults;
|
||||
|
||||
// Keys and expected value types.
|
||||
/// Keys and expected value types.
|
||||
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
|
||||
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
|
||||
|
||||
/// Holds the configurations from a sync server and mobileconfig.
|
||||
@property NSMutableDictionary *syncState;
|
||||
@property NSDictionary *syncState;
|
||||
@property NSMutableDictionary *configState;
|
||||
|
||||
/// Was --debug passed as an argument to this process?
|
||||
@property(readonly, nonatomic) BOOL debugFlag;
|
||||
|
||||
/// Holds the last processed hash of the static rules list.
|
||||
@property(atomic) NSDictionary *cachedStaticRules;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTConfigurator
|
||||
@@ -42,7 +49,10 @@ NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
|
||||
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 kSyncProxyConfigKey = @"SyncProxyConfiguration";
|
||||
static NSString *const kSyncEnableCleanSyncEventUpload = @"SyncEnableCleanSyncEventUpload";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
@@ -57,11 +67,16 @@ static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
|
||||
static NSString *const kEnableSilentModeKey = @"EnableSilentMode";
|
||||
static NSString *const kAboutTextKey = @"AboutText";
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
|
||||
static NSString *const kRemountUSBBlockMessage = @"RemountUSBBlockMessage";
|
||||
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
@@ -73,16 +88,44 @@ static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters
|
||||
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
static NSString *const kEventLogPath = @"EventLogPath";
|
||||
static NSString *const kSpoolDirectory = @"SpoolDirectory";
|
||||
static NSString *const kSpoolDirectoryFileSizeThresholdKB = @"SpoolDirectoryFileSizeThresholdKB";
|
||||
static NSString *const kSpoolDirectorySizeThresholdMB = @"SpoolDirectorySizeThresholdMB";
|
||||
static NSString *const kSpoolDirectoryEventMaxFlushTimeSec = @"SpoolDirectoryEventMaxFlushTimeSec";
|
||||
|
||||
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
|
||||
|
||||
static NSString *const kEnableSystemExtension = @"EnableSystemExtension";
|
||||
static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
|
||||
static NSString *const kIgnoreOtherEndpointSecurityClients = @"IgnoreOtherEndpointSecurityClients";
|
||||
static NSString *const kEnableDebugLogging = @"EnableDebugLogging";
|
||||
|
||||
static NSString *const kEnableBackwardsCompatibleContentEncoding =
|
||||
@"EnableBackwardsCompatibleContentEncoding";
|
||||
|
||||
static NSString *const kFCMProject = @"FCMProject";
|
||||
static NSString *const kFCMEntity = @"FCMEntity";
|
||||
static NSString *const kFCMAPIKey = @"FCMAPIKey";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kEnableTransitiveWhitelistingKey = @"EnableTransitiveWhitelisting";
|
||||
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
|
||||
static NSString *const kFailClosedKey = @"FailClosed";
|
||||
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
|
||||
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
|
||||
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
|
||||
static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting";
|
||||
static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
|
||||
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";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
@@ -98,37 +141,56 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
Class string = [NSString class];
|
||||
Class data = [NSData class];
|
||||
Class array = [NSArray class];
|
||||
Class dictionary = [NSDictionary class];
|
||||
_syncServerKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kEnableTransitiveWhitelistingKey : number,
|
||||
kWhitelistRegexKey : re,
|
||||
kBlacklistRegexKey : re,
|
||||
kEnableTransitiveRulesKey : number,
|
||||
kEnableTransitiveRulesKeyDeprecated : number,
|
||||
kAllowedPathRegexKey : re,
|
||||
kAllowedPathRegexKeyDeprecated : re,
|
||||
kBlockedPathRegexKey : re,
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kBlockUSBMountKey : number,
|
||||
kRemountUSBModeKey : array,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
kSyncCleanRequired : number,
|
||||
kEnableAllEventUploadKey : number,
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kEnableTransitiveWhitelistingKey : number,
|
||||
kFailClosedKey : number,
|
||||
kEnableTransitiveRulesKey : number,
|
||||
kEnableTransitiveRulesKeyDeprecated : number,
|
||||
kFileChangesRegexKey : re,
|
||||
kFileChangesPrefixFiltersKey : array,
|
||||
kWhitelistRegexKey : re,
|
||||
kBlacklistRegexKey : re,
|
||||
kAllowedPathRegexKey : re,
|
||||
kAllowedPathRegexKeyDeprecated : re,
|
||||
kBlockedPathRegexKey : re,
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kBlockUSBMountKey : number,
|
||||
kRemountUSBModeKey : array,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey: number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kEnableSilentModeKey : number,
|
||||
kAboutTextKey : string,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kBannedUSBBlockMessage : string,
|
||||
kRemountUSBBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
kMachineIDKey : string,
|
||||
@@ -138,13 +200,32 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
kSpoolDirectory : string,
|
||||
kSpoolDirectoryFileSizeThresholdKB : number,
|
||||
kSpoolDirectorySizeThresholdMB : number,
|
||||
kSpoolDirectoryEventMaxFlushTimeSec : number,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSystemExtension : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
kEnableDebugLogging : number,
|
||||
kEnableBackwardsCompatibleContentEncoding : number,
|
||||
kFCMProject : string,
|
||||
kFCMEntity : string,
|
||||
kFCMAPIKey : string,
|
||||
kMetricFormat : string,
|
||||
kMetricURL : string,
|
||||
kMetricExportInterval : number,
|
||||
kMetricExportTimeout : number,
|
||||
kMetricExtraLabels : dictionary,
|
||||
kEnableAllEventUploadKey : number,
|
||||
kDisableUnknownEventUploadKey : number,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
_configState = [self readForcedConfig];
|
||||
[self cacheStaticRules];
|
||||
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
|
||||
_debugFlag = [[NSProcessInfo processInfo].arguments containsObject:@"--debug"];
|
||||
[self startWatchingDefaults];
|
||||
}
|
||||
return self;
|
||||
@@ -194,11 +275,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
|
||||
+ (NSSet *)keyPathsForValuesAffectingAllowlistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlocklistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
@@ -210,6 +291,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -218,6 +303,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSilentMode {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingAboutText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -298,15 +391,91 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectory {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectoryFileSizeThresholdKB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectorySizeThresholdMB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectoryEventMaxFlushTimeSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableTransitiveWhitelisting {
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableTransitiveRules {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingDisableUnknownEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableForkAndExitLogging {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingIgnoreOtherEndpointSecurityClients {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableDebugLogging {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFcmEntity {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFcmAPIKey {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFcmEnabled {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableBadSignatureProtection {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlockUSBMount {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBannedUSBBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBMode {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUsbBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
@@ -329,37 +498,64 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
|
||||
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
|
||||
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
|
||||
} else {
|
||||
LOGW(@"Ignoring request to change client mode to %ld", newMode);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)enableTransitiveWhitelisting {
|
||||
NSNumber *n = self.syncState[kEnableTransitiveWhitelistingKey];
|
||||
if (n) {
|
||||
return [n boolValue];
|
||||
}
|
||||
return [self.configState[kEnableTransitiveWhitelistingKey] boolValue];
|
||||
- (BOOL)failClosed {
|
||||
NSNumber *n = self.configState[kFailClosedKey];
|
||||
if (n) return [n boolValue];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kEnableTransitiveWhitelistingKey value:@(enabled)];
|
||||
- (BOOL)enableTransitiveRules {
|
||||
NSNumber *n = self.syncState[kEnableTransitiveRulesKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
n = self.syncState[kEnableTransitiveRulesKeyDeprecated];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
n = self.configState[kEnableTransitiveRulesKeyDeprecated];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kEnableTransitiveRulesKey] boolValue];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)whitelistPathRegex {
|
||||
return self.syncState[kWhitelistRegexKey] ?: self.configState[kWhitelistRegexKey];
|
||||
- (void)setEnableTransitiveRules:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kEnableTransitiveRulesKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kWhitelistRegexKey value:re];
|
||||
- (NSRegularExpression *)allowedPathRegex {
|
||||
NSRegularExpression *r = self.syncState[kAllowedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
r = self.syncState[kAllowedPathRegexKeyDeprecated];
|
||||
if (r) return r;
|
||||
|
||||
r = self.configState[kAllowedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
return self.configState[kAllowedPathRegexKeyDeprecated];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)blacklistPathRegex {
|
||||
return self.syncState[kBlacklistRegexKey] ?: self.configState[kBlacklistRegexKey];
|
||||
- (void)setSyncServerAllowedPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kAllowedPathRegexKey value:re];
|
||||
}
|
||||
|
||||
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kBlacklistRegexKey value:re];
|
||||
- (NSRegularExpression *)blockedPathRegex {
|
||||
NSRegularExpression *r = self.syncState[kBlockedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
r = self.syncState[kBlockedPathRegexKeyDeprecated];
|
||||
if (r) return r;
|
||||
|
||||
r = self.configState[kBlockedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
return self.configState[kBlockedPathRegexKeyDeprecated];
|
||||
}
|
||||
|
||||
- (void)setSyncServerBlockedPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kBlockedPathRegexKey value:re];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)fileChangesRegex {
|
||||
@@ -370,21 +566,44 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
NSArray *filters = self.configState[kFileChangesPrefixFiltersKey];
|
||||
for (id filter in filters) {
|
||||
if (![filter isKindOfClass:[NSString class]]) {
|
||||
LOGE(@"Ignoring FileChangesPrefixFilters: array contains a non-string %@", filter);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
- (void)setRemountUSBMode:(NSArray<NSString *> *)args {
|
||||
[self updateSyncStateForKey:kRemountUSBModeKey value:args];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)remountUSBMode {
|
||||
NSArray<NSString *> *args = self.syncState[kRemountUSBModeKey];
|
||||
if (!args) {
|
||||
args = (NSArray<NSString *> *)self.configState[kRemountUSBModeKey];
|
||||
}
|
||||
for (id arg in args) {
|
||||
if (![arg isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, SNTRule *> *)staticRules {
|
||||
return self.cachedStaticRules;
|
||||
}
|
||||
|
||||
- (NSURL *)syncBaseURL {
|
||||
NSString *urlString = self.configState[kSyncBaseURLKey];
|
||||
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
if (urlString && !url) LOGW(@"SyncBaseURL is not a valid URL!");
|
||||
return url;
|
||||
}
|
||||
|
||||
- (NSDictionary *)syncProxyConfig {
|
||||
return self.configState[kSyncProxyConfigKey];
|
||||
}
|
||||
|
||||
- (BOOL)enablePageZeroProtection {
|
||||
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
|
||||
return number ? [number boolValue] : YES;
|
||||
@@ -395,6 +614,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableSilentMode {
|
||||
NSNumber *number = self.configState[kEnableSilentModeKey];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (NSString *)aboutText {
|
||||
return self.configState[kAboutTextKey];
|
||||
}
|
||||
|
||||
- (NSURL *)moreInfoURL {
|
||||
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
|
||||
}
|
||||
@@ -415,6 +643,21 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return self.configState[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)bannedUSBBlockMessage {
|
||||
if (!self.configState[kBannedUSBBlockMessage]) {
|
||||
return @"The following device has been blocked from mounting.";
|
||||
}
|
||||
|
||||
return self.configState[kBannedUSBBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)remountUSBBlockMessage {
|
||||
if (!self.configState[kRemountUSBBlockMessage]) {
|
||||
return @"The following device has been remounted with reduced permissions.";
|
||||
}
|
||||
return self.configState[kRemountUSBBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configState[kModeNotificationMonitor];
|
||||
}
|
||||
@@ -502,30 +745,176 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (SNTEventLogType)eventLogType {
|
||||
NSString *s = [self.configState[kEventLogType] lowercaseString];
|
||||
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
|
||||
NSString *logType = [self.configState[kEventLogType] lowercaseString];
|
||||
if ([logType isEqualToString:@"protobuf"]) {
|
||||
return SNTEventLogTypeProtobuf;
|
||||
} else if ([logType isEqualToString:@"syslog"]) {
|
||||
return SNTEventLogTypeSyslog;
|
||||
} else if ([logType isEqualToString:@"null"]) {
|
||||
return SNTEventLogTypeNull;
|
||||
} else if ([logType isEqualToString:@"file"]) {
|
||||
return SNTEventLogTypeFilelog;
|
||||
} else {
|
||||
return SNTEventLogTypeFilelog;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
- (NSString *)spoolDirectory {
|
||||
return self.configState[kSpoolDirectory] ?: @"/var/db/santa/spool";
|
||||
}
|
||||
|
||||
- (NSUInteger)spoolDirectoryFileSizeThresholdKB {
|
||||
return self.configState[kSpoolDirectoryFileSizeThresholdKB]
|
||||
? [self.configState[kSpoolDirectoryFileSizeThresholdKB] unsignedIntegerValue]
|
||||
: 250;
|
||||
}
|
||||
|
||||
- (NSUInteger)spoolDirectorySizeThresholdMB {
|
||||
return self.configState[kSpoolDirectorySizeThresholdMB]
|
||||
? [self.configState[kSpoolDirectorySizeThresholdMB] unsignedIntegerValue]
|
||||
: 100;
|
||||
}
|
||||
|
||||
- (float)spoolDirectoryEventMaxFlushTimeSec {
|
||||
return self.configState[kSpoolDirectoryEventMaxFlushTimeSec]
|
||||
? [self.configState[kSpoolDirectoryEventMaxFlushTimeSec] floatValue]
|
||||
: 15.0;
|
||||
}
|
||||
|
||||
- (BOOL)enableMachineIDDecoration {
|
||||
NSNumber *number = self.configState[kEnableMachineIDDecoration];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableSystemExtension {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:@"/Library/Extensions/santa-driver.kext"]) return YES;
|
||||
NSNumber *number = self.configState[kEnableSystemExtension];
|
||||
return number ? [number boolValue] : YES;
|
||||
- (BOOL)enableCleanSyncEventUpload {
|
||||
NSNumber *number = self.configState[kSyncEnableCleanSyncEventUpload];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableAllEventUpload {
|
||||
NSNumber *n = self.syncState[kEnableAllEventUploadKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kEnableAllEventUploadKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setEnableAllEventUpload:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kEnableAllEventUploadKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (BOOL)disableUnknownEventUpload {
|
||||
NSNumber *n = self.syncState[kDisableUnknownEventUploadKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kDisableUnknownEventUploadKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setDisableUnknownEventUpload:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kDisableUnknownEventUploadKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (BOOL)enableForkAndExitLogging {
|
||||
NSNumber *number = self.configState[kEnableForkAndExitLogging];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)ignoreOtherEndpointSecurityClients {
|
||||
NSNumber *number = self.configState[kIgnoreOtherEndpointSecurityClients];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableDebugLogging {
|
||||
NSNumber *number = self.configState[kEnableDebugLogging];
|
||||
return [number boolValue] || self.debugFlag;
|
||||
}
|
||||
|
||||
- (BOOL)enableBackwardsCompatibleContentEncoding {
|
||||
NSNumber *number = self.configState[kEnableBackwardsCompatibleContentEncoding];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (NSString *)fcmProject {
|
||||
return self.configState[kFCMProject];
|
||||
}
|
||||
|
||||
- (NSString *)fcmEntity {
|
||||
return self.configState[kFCMEntity];
|
||||
}
|
||||
|
||||
- (NSString *)fcmAPIKey {
|
||||
return self.configState[kFCMAPIKey];
|
||||
}
|
||||
|
||||
- (BOOL)fcmEnabled {
|
||||
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
|
||||
}
|
||||
|
||||
- (void)setBlockUSBMount:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kBlockUSBMountKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (BOOL)blockUSBMount {
|
||||
NSNumber *n = self.syncState[kBlockUSBMountKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kBlockUSBMountKey] boolValue];
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns YES if all of the necessary options are set to export metrics, NO
|
||||
/// otherwise.
|
||||
///
|
||||
- (BOOL)exportMetrics {
|
||||
return [self metricFormat] != SNTMetricFormatTypeUnknown &&
|
||||
![self.configState[kMetricURL] isEqualToString:@""];
|
||||
}
|
||||
|
||||
- (SNTMetricFormatType)metricFormat {
|
||||
NSString *normalized = [self.configState[kMetricFormat] lowercaseString];
|
||||
|
||||
normalized = [normalized stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
if ([normalized isEqualToString:@"rawjson"]) {
|
||||
return SNTMetricFormatTypeRawJSON;
|
||||
} else if ([normalized isEqualToString:@"monarchjson"]) {
|
||||
return SNTMetricFormatTypeMonarchJSON;
|
||||
} else {
|
||||
return NO;
|
||||
return SNTMetricFormatTypeUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURL *)metricURL {
|
||||
return [NSURL URLWithString:self.configState[kMetricURL]];
|
||||
}
|
||||
|
||||
// Returns a default value of 30 (for 30 seconds).
|
||||
- (NSUInteger)metricExportInterval {
|
||||
NSNumber *configuredInterval = self.configState[kMetricExportInterval];
|
||||
|
||||
if (configuredInterval == nil) {
|
||||
return 30;
|
||||
}
|
||||
return [configuredInterval unsignedIntegerValue];
|
||||
}
|
||||
|
||||
// Returns a default value of 30 (for 30 seconds).
|
||||
- (NSUInteger)metricExportTimeout {
|
||||
NSNumber *configuredInterval = self.configState[kMetricExportTimeout];
|
||||
|
||||
if (configuredInterval == nil) {
|
||||
return 30;
|
||||
}
|
||||
return [configuredInterval unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (NSDictionary *)extraMetricLabels {
|
||||
return self.configState[kMetricExtraLabels];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
///
|
||||
@@ -549,7 +938,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
// Only santad should read this file.
|
||||
if (geteuid() != 0) return nil;
|
||||
NSMutableDictionary *syncState =
|
||||
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
|
||||
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
|
||||
for (NSString *key in syncState.allKeys) {
|
||||
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
|
||||
@@ -572,11 +961,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
if (geteuid() != 0) return;
|
||||
// Either remove
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
syncState[kWhitelistRegexKey] = [syncState[kWhitelistRegexKey] pattern];
|
||||
syncState[kBlacklistRegexKey] = [syncState[kBlacklistRegexKey] pattern];
|
||||
syncState[kAllowedPathRegexKey] = [syncState[kAllowedPathRegexKey] pattern];
|
||||
syncState[kBlockedPathRegexKey] = [syncState[kBlockedPathRegexKey] pattern];
|
||||
[syncState writeToFile:kSyncStateFilePath atomically:YES];
|
||||
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
|
||||
ofItemAtPath:kSyncStateFilePath error:NULL];
|
||||
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0600}
|
||||
ofItemAtPath:kSyncStateFilePath
|
||||
error:NULL];
|
||||
}
|
||||
|
||||
- (void)clearSyncState {
|
||||
@@ -611,8 +1001,9 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (void)startWatchingDefaults {
|
||||
// Only santad should listen.
|
||||
if (geteuid() != 0) return;
|
||||
// Only com.google.santa.daemon should listen.
|
||||
NSString *processName = [[NSProcessInfo processInfo] processName];
|
||||
if (![processName isEqualToString:@"com.google.santa.daemon"]) return;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(defaultsChanged:)
|
||||
name:NSUserDefaultsDidChangeNotification
|
||||
@@ -630,6 +1021,24 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
///
|
||||
- (void)handleChange {
|
||||
self.configState = [self readForcedConfig];
|
||||
[self cacheStaticRules];
|
||||
}
|
||||
|
||||
///
|
||||
/// Processes the StaticRules key to create SNTRule objects and caches them for quick use
|
||||
///
|
||||
- (void)cacheStaticRules {
|
||||
NSArray *staticRules = self.configState[kStaticRules];
|
||||
if (![staticRules isKindOfClass:[NSArray class]]) return;
|
||||
|
||||
NSMutableDictionary<NSString *, SNTRule *> *rules =
|
||||
[NSMutableDictionary dictionaryWithCapacity:staticRules.count];
|
||||
for (id rule in staticRules) {
|
||||
if (![rule isKindOfClass:[NSDictionary class]]) return;
|
||||
SNTRule *r = [[SNTRule alloc] initWithDictionary:rule];
|
||||
rules[r.identifier] = r;
|
||||
}
|
||||
self.cachedStaticRules = [rules copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
27
Source/common/SNTDeviceEvent.h
Normal file
27
Source/common/SNTDeviceEvent.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Copyright 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SNTDeviceEvent : NSObject <NSSecureCoding>
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname;
|
||||
|
||||
@property NSString *mntonname;
|
||||
@property NSString *mntfromname;
|
||||
@property NSArray<NSString *> *remountArgs;
|
||||
|
||||
- (NSString *)readableRemountArgs;
|
||||
|
||||
@end
|
||||
63
Source/common/SNTDeviceEvent.m
Normal file
63
Source/common/SNTDeviceEvent.m
Normal file
@@ -0,0 +1,63 @@
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
|
||||
@implementation SNTDeviceEvent
|
||||
|
||||
#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]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = mntonname;
|
||||
_mntfromname = mntfromname;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.mntonname, @"mntonname");
|
||||
ENCODE(self.mntfromname, @"mntfromname");
|
||||
ENCODE(self.remountArgs, @"remountArgs");
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = DECODE(NSString, @"mntonname");
|
||||
_mntfromname = DECODE(NSString, @"mntfromname");
|
||||
_remountArgs = DECODEARRAY(NSString, @"remountArgs");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTDeviceEvent '%@' -> '%@' (with permissions: [%@]",
|
||||
self.mntfromname, self.mntonname,
|
||||
[self.remountArgs componentsJoinedByString:@", "]];
|
||||
}
|
||||
|
||||
- (NSString *)readableRemountArgs {
|
||||
NSMutableArray<NSString *> *readable = [NSMutableArray array];
|
||||
for (NSString *arg in self.remountArgs) {
|
||||
if ([arg isEqualToString:@"rdonly"]) {
|
||||
[readable addObject:@"read-only"];
|
||||
} else if ([arg isEqualToString:@"noexec"]) {
|
||||
[readable addObject:@"block executables"];
|
||||
} else {
|
||||
[readable addObject:arg];
|
||||
}
|
||||
}
|
||||
return [readable componentsJoinedByString:@", "];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -12,6 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
@@ -32,6 +33,14 @@
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// Convenience initializer.
|
||||
///
|
||||
/// @param esFile Pointer to an es_file_t provided by the EndpointSecurity framework.
|
||||
/// Assumes that the path is a resolved path.
|
||||
///
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// Convenience initializer.
|
||||
///
|
||||
@@ -40,7 +49,6 @@
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
|
||||
///
|
||||
/// Initializer for already resolved paths.
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -15,8 +15,8 @@
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <fmdb/FMDB.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#import <fmdb/FMDB.h>
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
// Simple class to hold the data of a mach_header and the offset within the file
|
||||
// in which that header was found.
|
||||
@@ -49,6 +50,7 @@
|
||||
@property NSFileHandle *fileHandle;
|
||||
@property NSUInteger fileSize;
|
||||
@property NSString *fileOwnerHomeDir;
|
||||
@property NSString *sha256Storage;
|
||||
|
||||
// Cached properties
|
||||
@property NSBundle *bundleRef;
|
||||
@@ -64,6 +66,26 @@
|
||||
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
|
||||
struct stat fileStat;
|
||||
if (path.length) {
|
||||
lstat(path.UTF8String, &fileStat);
|
||||
}
|
||||
return [self initWithResolvedPath:path stat:&fileStat error:error];
|
||||
}
|
||||
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile error:(NSError **)error {
|
||||
return [self initWithResolvedPath:@(esFile->path.data) stat:&esFile->stat error:error];
|
||||
}
|
||||
|
||||
- (instancetype)initWithResolvedPath:(NSString *)path
|
||||
stat:(const struct stat *)fileStat
|
||||
error:(NSError **)error {
|
||||
if (!fileStat) {
|
||||
// This is a programming error. Bail.
|
||||
LOGE(@"NULL stat buffer unsupported");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_path = path;
|
||||
@@ -77,9 +99,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return nil;
|
||||
}
|
||||
|
||||
struct stat fileStat;
|
||||
lstat(_path.UTF8String, &fileStat);
|
||||
if (!((S_IFMT & fileStat.st_mode) == S_IFREG)) {
|
||||
if (!((S_IFMT & fileStat->st_mode) == S_IFREG)) {
|
||||
if (error) {
|
||||
NSString *errStr = [NSString stringWithFormat:@"Non regular file: %s", strerror(errno)];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
@@ -89,12 +109,12 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_fileSize = fileStat.st_size;
|
||||
_fileSize = fileStat->st_size;
|
||||
|
||||
if (_fileSize == 0) return nil;
|
||||
|
||||
if (fileStat.st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat.st_uid);
|
||||
if (fileStat->st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat->st_uid);
|
||||
if (pwd) {
|
||||
_fileOwnerHomeDir = @(pwd->pw_dir);
|
||||
}
|
||||
@@ -181,30 +201,27 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1_Final(digest, &c1);
|
||||
NSString *const SHA1FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
*sha1 = [[NSString alloc]
|
||||
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19]];
|
||||
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
|
||||
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
|
||||
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19]];
|
||||
}
|
||||
if (sha256) {
|
||||
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256_Final(digest, &c256);
|
||||
NSString *const SHA256FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
|
||||
*sha256 = [[NSString alloc]
|
||||
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19], digest[20],
|
||||
digest[21], digest[22], digest[23], digest[24],
|
||||
digest[25], digest[26], digest[27], digest[28],
|
||||
digest[29], digest[30], digest[31]];
|
||||
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
|
||||
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
|
||||
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19], digest[20], digest[21], digest[22],
|
||||
digest[23], digest[24], digest[25], digest[26], digest[27], digest[28],
|
||||
digest[29], digest[30], digest[31]];
|
||||
}
|
||||
} @finally {
|
||||
free(chunk);
|
||||
@@ -218,9 +235,13 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
- (NSString *)SHA256 {
|
||||
NSString *sha256;
|
||||
[self hashSHA1:NULL SHA256:&sha256];
|
||||
return sha256;
|
||||
// Memoize the value
|
||||
if (!self.sha256Storage) {
|
||||
NSString *sha256;
|
||||
[self hashSHA1:NULL SHA256:&sha256];
|
||||
self.sha256Storage = sha256;
|
||||
}
|
||||
return self.sha256Storage;
|
||||
}
|
||||
|
||||
#pragma mark File Type Info
|
||||
@@ -292,15 +313,15 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
- (BOOL)isMissingPageZero {
|
||||
// This method only checks i386 arch because the kernel enforces this for other archs
|
||||
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
|
||||
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86
|
||||
cpuSubType:CPU_SUBTYPE_I386_ALL]];
|
||||
MachHeaderWithOffset *x86Header =
|
||||
self.machHeaders[[self nameForCPUType:CPU_TYPE_X86 cpuSubType:CPU_SUBTYPE_I386_ALL]];
|
||||
if (!x86Header) return NO;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
|
||||
if (mh->filetype != MH_EXECUTE) return NO;
|
||||
|
||||
NSRange range = NSMakeRange(x86Header.offset + sizeof(struct mach_header),
|
||||
sizeof(struct segment_command));
|
||||
NSRange range =
|
||||
NSMakeRange(x86Header.offset + sizeof(struct mach_header), sizeof(struct segment_command));
|
||||
NSData *lcData = [self safeSubdataWithRange:range];
|
||||
if (!lcData) return NO;
|
||||
|
||||
@@ -310,9 +331,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
struct load_command *lc = (struct load_command *)[lcData bytes];
|
||||
if (lc->cmd == LC_SEGMENT) {
|
||||
struct segment_command *segment = (struct segment_command *)lc;
|
||||
if (segment->vmaddr == 0 && segment->vmsize != 0 &&
|
||||
segment->initprot == 0 && segment->maxprot == 0 &&
|
||||
strcmp("__PAGEZERO", segment->segname) == 0) {
|
||||
if (segment->vmaddr == 0 && segment->vmsize != 0 && segment->initprot == 0 &&
|
||||
segment->maxprot == 0 && strcmp("__PAGEZERO", segment->segname) == 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
@@ -362,7 +382,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
while (pathComponents.count > 1) {
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
if ([bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
|
||||
if (!ancestor ||
|
||||
if ((!ancestor && bndl.bundlePath.pathExtension.length) ||
|
||||
[[self allowedAncestorExtensions] containsObject:bndl.bundlePath.pathExtension]) {
|
||||
bundle = bndl;
|
||||
}
|
||||
@@ -376,7 +396,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
- (NSBundle *)bundle {
|
||||
if (!self.bundleRef) {
|
||||
self.bundleRef =
|
||||
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
|
||||
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
|
||||
}
|
||||
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
|
||||
}
|
||||
@@ -417,8 +437,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
- (NSString *)bundleName {
|
||||
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
|
||||
[[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description]
|
||||
?: [[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleVersion {
|
||||
@@ -467,8 +487,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
|
||||
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0,
|
||||
4096)]];
|
||||
NSData *machHeader =
|
||||
[self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0, 4096)]];
|
||||
if (machHeader) {
|
||||
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
|
||||
@@ -551,24 +571,51 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
for (uint32_t i = 0; i < ncmds; ++i) {
|
||||
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
|
||||
if (!cmdData) return nil;
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
|
||||
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
|
||||
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) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
} else {
|
||||
struct segment_command *lc = (struct segment_command *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT && memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
|
||||
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
|
||||
for (uint32_t i = 0; i < nsects; ++i) {
|
||||
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
|
||||
if (!sectData) return nil;
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
|
||||
uint64_t sectoffset, sectsize = 0;
|
||||
BOOL found = NO;
|
||||
if (is64) {
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
sectoffset = sect->offset;
|
||||
sectsize = sect->size;
|
||||
found = YES;
|
||||
}
|
||||
} else {
|
||||
struct section *sect = (struct section *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
sectoffset = sect->offset;
|
||||
sectsize = sect->size;
|
||||
found = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
NSData *plistData =
|
||||
[self safeSubdataWithRange:NSMakeRange(mhwo.offset + sectoffset, sectsize)];
|
||||
if (!plistData) return nil;
|
||||
NSDictionary *plist;
|
||||
plist = [NSPropertyListSerialization propertyListWithData:plistData
|
||||
@@ -640,9 +687,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
NSURL *dbPath = [NSURL fileURLWithPathComponents:@[
|
||||
fileOwnerHomeDir,
|
||||
@"Library",
|
||||
@"Preferences",
|
||||
fileOwnerHomeDir, @"Library", @"Preferences",
|
||||
@"com.apple.LaunchServices.QuarantineEventsV2"
|
||||
]];
|
||||
FMDatabase *db = [FMDatabase databaseWithPath:[dbPath absoluteString]];
|
||||
@@ -661,7 +706,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
quarantineDict[@"LSQuarantineDataURL"] = [NSURL URLWithString:dataURLString];
|
||||
quarantineDict[@"LSQuarantineOriginURL"] = [NSURL URLWithString:originURLString];
|
||||
quarantineDict[@"LSQuarantineTimestamp"] =
|
||||
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
|
||||
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
|
||||
|
||||
self.quarantineDict = quarantineDict;
|
||||
}
|
||||
|
||||
@@ -39,9 +39,8 @@
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
|
||||
XCTAssertEqualObjects(sut.path, @"/bin/ls");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/AppleFileServer"];
|
||||
XCTAssertEqualObjects(sut.path, @"/System/Library/CoreServices/AppleFileServer.app/"
|
||||
@"Contents/MacOS/AppleFileServer");
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/DirectoryService"];
|
||||
XCTAssertEqualObjects(sut.path, @"/usr/libexec/dspluginhelperd");
|
||||
}
|
||||
|
||||
- (void)testSHA1 {
|
||||
@@ -72,7 +71,6 @@
|
||||
XCTAssertTrue(sut.isExecutable);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
@@ -92,9 +90,8 @@
|
||||
}
|
||||
|
||||
- (void)testKext {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:
|
||||
@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc]
|
||||
initWithPath:@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isKext);
|
||||
@@ -106,7 +103,7 @@
|
||||
}
|
||||
|
||||
- (void)testDylibs {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/libsqlite3.dylib"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/system/libsystem_platform.dylib"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isDylib);
|
||||
@@ -220,8 +217,7 @@
|
||||
}
|
||||
|
||||
- (void)testNonBundle {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
|
||||
@@ -231,10 +227,16 @@
|
||||
}
|
||||
|
||||
- (void)testEmbeddedInfoPlist {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"32bitplist"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
XCTAssertEqualObjects([sut infoPlist][@"CFBundleShortVersionString"], @"1.0");
|
||||
XCTAssertEqualObjects([sut infoPlist][@"CFBundleIdentifier"], @"com.google.i386plist");
|
||||
|
||||
// csreq is installed on all machines with Xcode installed. If you're running these tests,
|
||||
// it should be available..
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
}
|
||||
|
||||
|
||||
34
Source/common/SNTKVOManager.h
Normal file
34
Source/common/SNTKVOManager.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#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.
|
||||
typedef void (^KVOCallback)(id oldValue, id newValue);
|
||||
|
||||
@interface SNTKVOManager : NSObject
|
||||
|
||||
// Add an observer for the selector on the given object. When a KVO notification
|
||||
// is received, the callback is called. If the notification contains objects that
|
||||
// are not of the expectedType, nil is passed as the argument to the callback.
|
||||
// The observer is removed when the returned instance is deallocated.
|
||||
- (instancetype)initWithObject:(id)object
|
||||
selector:(SEL)selector
|
||||
type:(Class)expectedType
|
||||
callback:(KVOCallback)callback;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
72
Source/common/SNTKVOManager.mm
Normal file
72
Source/common/SNTKVOManager.mm
Normal file
@@ -0,0 +1,72 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTKVOManager.h"
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
@interface SNTKVOManager ()
|
||||
@property KVOCallback callback;
|
||||
@property Class expectedType;
|
||||
@property NSString *keyPath;
|
||||
@property id object;
|
||||
@end
|
||||
|
||||
@implementation SNTKVOManager
|
||||
|
||||
- (instancetype)initWithObject:(id)object
|
||||
selector:(SEL)selector
|
||||
type:(Class)expectedType
|
||||
callback:(KVOCallback)callback {
|
||||
self = [super self];
|
||||
if (self) {
|
||||
NSString *selectorName = NSStringFromSelector(selector);
|
||||
if (![object respondsToSelector:selector]) {
|
||||
LOGE(@"Attempt to add observer for an unknown selector (%@) for object (%@)", selectorName,
|
||||
[object class]);
|
||||
return nil;
|
||||
}
|
||||
|
||||
_object = object;
|
||||
_keyPath = selectorName;
|
||||
_expectedType = expectedType;
|
||||
_callback = callback;
|
||||
|
||||
[object addObserver:self
|
||||
forKeyPath:selectorName
|
||||
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
|
||||
context:NULL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self.object removeObserver:self forKeyPath:self.keyPath context:NULL];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary<NSString *, id> *)change
|
||||
context:(void *)context {
|
||||
id oldValue = [change[NSKeyValueChangeOldKey] isKindOfClass:self.expectedType]
|
||||
? change[NSKeyValueChangeOldKey]
|
||||
: nil;
|
||||
id newValue = [change[NSKeyValueChangeNewKey] isKindOfClass:self.expectedType]
|
||||
? change[NSKeyValueChangeNewKey]
|
||||
: nil;
|
||||
|
||||
self.callback(oldValue, newValue);
|
||||
}
|
||||
|
||||
@end
|
||||
129
Source/common/SNTKVOManagerTest.mm
Normal file
129
Source/common/SNTKVOManagerTest.mm
Normal file
@@ -0,0 +1,129 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTKVOManager.h"
|
||||
|
||||
@interface Foo : NSObject
|
||||
@property NSNumber *propNumber;
|
||||
@property NSArray *propArray;
|
||||
@property id propId;
|
||||
@end
|
||||
|
||||
@implementation Foo
|
||||
@end
|
||||
|
||||
@interface SNTKVOManagerTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTKVOManagerTest
|
||||
|
||||
- (void)testInvalidSelector {
|
||||
Foo *foo = [[Foo alloc] init];
|
||||
|
||||
SNTKVOManager *kvo = [[SNTKVOManager alloc] initWithObject:foo
|
||||
selector:NSSelectorFromString(@"doesNotExist")
|
||||
type:[NSNumber class]
|
||||
callback:^(id, id){
|
||||
}];
|
||||
|
||||
XCTAssertNil(kvo);
|
||||
}
|
||||
|
||||
- (void)testNormalOperation {
|
||||
Foo *foo = [[Foo alloc] init];
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
int origVal = 123;
|
||||
int update1 = 456;
|
||||
int update2 = 789;
|
||||
|
||||
foo.propNumber = @(origVal);
|
||||
|
||||
// Store the values from the callback to test against expected values
|
||||
__block int oldVal;
|
||||
__block int newVal;
|
||||
|
||||
SNTKVOManager *kvo =
|
||||
[[SNTKVOManager alloc] initWithObject:foo
|
||||
selector:@selector(propNumber)
|
||||
type:[NSNumber class]
|
||||
callback:^(NSNumber *oldValue, NSNumber *newValue) {
|
||||
oldVal = [oldValue intValue];
|
||||
newVal = [newValue intValue];
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
XCTAssertNotNil(kvo);
|
||||
|
||||
// Ensure an update to the observed property triggers the callback
|
||||
foo.propNumber = @(update1);
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for first observable update");
|
||||
XCTAssertEqual(oldVal, origVal);
|
||||
XCTAssertEqual(newVal, update1);
|
||||
|
||||
// One more time why not
|
||||
foo.propNumber = @(update2);
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for second observable update");
|
||||
XCTAssertEqual(oldVal, update1);
|
||||
XCTAssertEqual(newVal, update2);
|
||||
}
|
||||
|
||||
- (void)testUnexpectedTypes {
|
||||
Foo *foo = [[Foo alloc] init];
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
NSString *origVal = @"any_val";
|
||||
NSString *update = @"new_val";
|
||||
foo.propId = origVal;
|
||||
|
||||
__block id oldVal;
|
||||
__block id newVal;
|
||||
|
||||
SNTKVOManager *kvo = [[SNTKVOManager alloc] initWithObject:foo
|
||||
selector:@selector(propId)
|
||||
type:[NSString class]
|
||||
callback:^(id oldValue, id newValue) {
|
||||
oldVal = oldValue;
|
||||
newVal = newValue;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
XCTAssertNotNil(kvo);
|
||||
|
||||
// Update to an unexpected type (here, NSNumber instead of NSString)
|
||||
foo.propId = @(123);
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for first observable update");
|
||||
XCTAssertEqualObjects(oldVal, origVal);
|
||||
XCTAssertNil(newVal);
|
||||
|
||||
// Update again with an expected type, ensure oldVal is now nil
|
||||
foo.propId = update;
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for first observable update");
|
||||
XCTAssertNil(oldVal);
|
||||
XCTAssertEqualObjects(newVal, update);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,141 +0,0 @@
|
||||
/// Copyright 2015 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 kernel <-> userspace
|
||||
///
|
||||
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifndef SANTA__COMMON__KERNELCOMMON_H
|
||||
#define SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
// Defines the name of the userclient class and the driver bundle ID.
|
||||
#define USERCLIENT_CLASS "com_google_SantaDriver"
|
||||
#define USERCLIENT_ID "com.google.santa-driver"
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
kSantaUserClientAllowBinary,
|
||||
kSantaUserClientAllowCompiler,
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientAcknowledgeBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientRemoveCacheEntry,
|
||||
kSantaUserClientCacheCount,
|
||||
kSantaUserClientCheckCache,
|
||||
kSantaUserClientCacheBucketCount,
|
||||
kSantaUserClientFilemodPrefixFilterAdd,
|
||||
kSantaUserClientFilemodPrefixFilterReset,
|
||||
|
||||
// Any methods supported by the driver should be added above this line to
|
||||
// ensure this remains the count of methods.
|
||||
kSantaUserClientNMethods,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
QUEUETYPE_DECISION,
|
||||
QUEUETYPE_LOG,
|
||||
} santa_queuetype_t;
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
// response methods.
|
||||
typedef enum {
|
||||
ACTION_UNSET = 0,
|
||||
|
||||
// REQUESTS
|
||||
ACTION_REQUEST_SHUTDOWN = 10,
|
||||
ACTION_REQUEST_BINARY = 11,
|
||||
|
||||
// RESPONSES
|
||||
ACTION_RESPOND_ALLOW = 20,
|
||||
ACTION_RESPOND_DENY = 21,
|
||||
ACTION_RESPOND_TOOLONG = 22,
|
||||
ACTION_RESPOND_ACK = 23,
|
||||
ACTION_RESPOND_ALLOW_COMPILER = 24,
|
||||
// The following response is stored only in the kernel decision cache.
|
||||
// It is removed by SNTCompilerController
|
||||
ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE = 25,
|
||||
|
||||
// NOTIFY
|
||||
ACTION_NOTIFY_EXEC = 30,
|
||||
ACTION_NOTIFY_WRITE = 31,
|
||||
ACTION_NOTIFY_RENAME = 32,
|
||||
ACTION_NOTIFY_LINK = 33,
|
||||
ACTION_NOTIFY_EXCHANGE = 34,
|
||||
ACTION_NOTIFY_DELETE = 35,
|
||||
ACTION_NOTIFY_WHITELIST = 36,
|
||||
|
||||
// ERROR
|
||||
ACTION_ERROR = 99,
|
||||
} santa_action_t;
|
||||
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || \
|
||||
x == ACTION_RESPOND_DENY || \
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER || \
|
||||
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
|
||||
|
||||
// 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;
|
||||
}
|
||||
// This _must not_ be used for anything security-sensitive. It exists solely to make
|
||||
// the msleep/wakeup calls easier.
|
||||
uint64_t unsafe_simple_id() const {
|
||||
return (((uint64_t)fsid << 32) | fileid);
|
||||
}
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
// Message struct that is sent down the IODataQueue.
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
santa_vnode_id_t vnode_id;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
// For file events, this is the process name.
|
||||
// For exec requests, this is the parent process name.
|
||||
// While process names can technically be 4*MAXPATHLEN, that never
|
||||
// actually happens, so only take MAXPATHLEN and throw away any excess.
|
||||
char pname[MAXPATHLEN];
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to a copy of the message.
|
||||
void *es_message;
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to an NSArray of the arguments.
|
||||
void *args_array;
|
||||
} santa_message_t;
|
||||
|
||||
// Used for the kSantaUserClientCacheBucketCount request.
|
||||
typedef struct {
|
||||
uint16_t per_bucket[1024];
|
||||
uint64_t start;
|
||||
} santa_bucket_count_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
@@ -13,27 +13,12 @@
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Logging definitions, for both kernel and user space.
|
||||
/// Logging definitions
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__LOGGING_H
|
||||
#define SANTA__COMMON__LOGGING_H
|
||||
|
||||
#ifdef KERNEL
|
||||
|
||||
#include <IOKit/IOLib.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#else // DEBUG
|
||||
#define LOGD(format, ...)
|
||||
#endif // DEBUG
|
||||
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
|
||||
#else // KERNEL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -56,7 +41,7 @@ typedef enum : NSUInteger {
|
||||
/// @param ... the arguments to format.
|
||||
///
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
__attribute__((format(__NSString__, 3, 4)));
|
||||
__attribute__((format(__NSString__, 3, 4)));
|
||||
|
||||
/// Simple logging macros
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
|
||||
@@ -64,10 +49,11 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
/// Get the logging level for this process.
|
||||
LogLevel EffectiveLogLevel();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
#endif // SANTA__COMMON__LOGGING_H
|
||||
|
||||
@@ -14,15 +14,11 @@
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
@@ -30,6 +26,21 @@ void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
LogLevel EffectiveLogLevel() {
|
||||
#ifdef DEBUG
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if ([SNTConfigurator configurator].enableDebugLogging) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
});
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static NSString *binaryName;
|
||||
@@ -39,11 +50,6 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
dispatch_once(&pred, ^{
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
|
||||
@@ -52,7 +58,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
}
|
||||
});
|
||||
|
||||
if (logLevel < level) return;
|
||||
if (EffectiveLogLevel() < level) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
@@ -83,11 +89,14 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
break;
|
||||
case LOG_LEVEL_INFO:
|
||||
levelName = "I";
|
||||
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
|
||||
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
|
||||
break;
|
||||
case LOG_LEVEL_DEBUG:
|
||||
levelName = "D";
|
||||
syslogLevel = ASL_LEVEL_DEBUG;
|
||||
// Log debug messages at the same ASL level as INFO.
|
||||
// While it would make sense to use DEBUG, watching debug-level logs
|
||||
// in Console means enabling all debug logs, which is absurdly noisy.
|
||||
syslogLevel = ASL_LEVEL_NOTICE;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
202
Source/common/SNTMetricSet.h
Normal file
202
Source/common/SNTMetricSet.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
/**
|
||||
* Provides an abstraction for various metric systems that will be exported to
|
||||
* monitoring systems via the MetricService. This is used to store internal
|
||||
* counters and metrics that can be exported to an external monitoring system.
|
||||
*
|
||||
* `SNTMetricSet` for storing and creating metrics and counters. This is
|
||||
* the externally visible interface
|
||||
* class.
|
||||
*
|
||||
* Metric classes:
|
||||
* * `SNTMetric` to store metric values broken down by "field" dimensions.
|
||||
* * subclasses of `SNTMetric` with suitable setters:
|
||||
* * `SNTMetricCounter`
|
||||
* * `SNTMetricGaugeInt64`
|
||||
* * `SNTMetricGaugeDouble`
|
||||
* * `SNTMetricString`
|
||||
* * `SNTMetricBool`
|
||||
*/
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricType) {
|
||||
SNTMetricTypeUnknown = 0,
|
||||
SNTMetricTypeConstantBool = 1,
|
||||
SNTMetricTypeConstantString = 2,
|
||||
SNTMetricTypeConstantInt64 = 3,
|
||||
SNTMetricTypeConstantDouble = 4,
|
||||
SNTMetricTypeGaugeBool = 5,
|
||||
SNTMetricTypeGaugeString = 6,
|
||||
SNTMetricTypeGaugeInt64 = 7,
|
||||
SNTMetricTypeGaugeDouble = 8,
|
||||
SNTMetricTypeCounter = 9,
|
||||
};
|
||||
|
||||
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType);
|
||||
|
||||
@interface SNTMetric : NSObject
|
||||
- (NSDictionary *)export;
|
||||
@end
|
||||
|
||||
@interface SNTMetricCounter : SNTMetric
|
||||
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricInt64Gauge : SNTMetric
|
||||
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricDoubleGauge : SNTMetric
|
||||
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricStringGauge : SNTMetric
|
||||
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricBooleanGauge : SNTMetric
|
||||
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
/**
|
||||
* A registry of metrics with associated fields.
|
||||
*/
|
||||
@interface SNTMetricSet : NSObject
|
||||
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username;
|
||||
|
||||
/* Returns a counter with the given name, field names and help
|
||||
* text, registered with the MetricSet.
|
||||
*
|
||||
* @param name The counter name, for example @"/proc/cpu".
|
||||
* @param fieldNames The counter's field names, for example @[@"result"].
|
||||
* @param helpText The counter's help description.
|
||||
* @return A counter with the given specification registered with this root.
|
||||
* The returned counter might have been created earlier with the same
|
||||
* specification.
|
||||
* @throw NSInternalInconsistencyException When trying to register a second
|
||||
* counter with the same name but a different schema as an existing one
|
||||
*/
|
||||
- (SNTMetricCounter *)counterWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text;
|
||||
|
||||
/**
|
||||
* Returns a shared global instance with default root labels and metrics registered.
|
||||
*/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Resets all the metrics in this set. Intended only for testing.
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
* Add a root label to the MetricSet.
|
||||
*/
|
||||
- (void)addRootLabel:(NSString *)label value:(NSString *)value;
|
||||
|
||||
/**
|
||||
* Remove a root label from the MetricSet.
|
||||
*/
|
||||
- (void)removeRootLabel:(NSString *)labelName;
|
||||
|
||||
/**
|
||||
* Returns a int64 gauge metric with the given Streamz name and help text,
|
||||
* registered with this MetricSet.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a double gauge metric with the given name and help text,
|
||||
* registered with this root.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a string gauge metric with the given name and help text,
|
||||
* registered with this metric set.
|
||||
*
|
||||
* @param name The metric name, for example @"/santa/mode".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a boolean gauge metric with the given name and help text,
|
||||
* registered with this metric set.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/** Creates a constant metric with a string value and no fields. */
|
||||
- (void)addConstantStringWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(NSString *)value;
|
||||
|
||||
/** Creates a constant metric with an integer value and no fields. */
|
||||
- (void)addConstantIntegerWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(long long)value;
|
||||
|
||||
/** Creates a constant metric with an integer value and no fields. */
|
||||
- (void)addConstantBooleanWithName:(NSString *)name helpText:(NSString *)helpText value:(BOOL)value;
|
||||
|
||||
/** Register a callback to get executed just before each export. */
|
||||
- (void)registerCallback:(void (^)(void))callback;
|
||||
|
||||
/** Export creates an NSDictionary of the state of the metrics */
|
||||
- (NSDictionary *)export;
|
||||
@end
|
||||
|
||||
// Returns a human readble string from an SNTMetricFormat type
|
||||
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format);
|
||||
|
||||
/** Normalizes dates in an exported dictionary to be ISO8601 timestamp strings in
|
||||
* UTC time.
|
||||
*/
|
||||
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
673
Source/common/SNTMetricSet.m
Normal file
673
Source/common/SNTMetricSet.m
Normal file
@@ -0,0 +1,673 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTMetricSet.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
NSString *typeStr;
|
||||
switch (metricType) {
|
||||
case SNTMetricTypeConstantBool: typeStr = @"SNTMetricTypeConstantBool"; break;
|
||||
case SNTMetricTypeConstantString: typeStr = @"SNTMetricTypeConstantString"; break;
|
||||
case SNTMetricTypeConstantInt64: typeStr = @"SNTMetricTypeConstantInt64"; break;
|
||||
case SNTMetricTypeConstantDouble: typeStr = @"SNTMetricTypeConstantDouble"; break;
|
||||
case SNTMetricTypeGaugeBool: typeStr = @"SNTMetricTypeGaugeBool"; break;
|
||||
case SNTMetricTypeGaugeString: typeStr = @"SNTMetricTypeGaugeString"; break;
|
||||
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
|
||||
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
|
||||
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
|
||||
default: typeStr = [NSString stringWithFormat:@"SNTMetricTypeUnknown %ld", metricType]; break;
|
||||
}
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* SNTMetricValue encapsulates the value of a metric along with the creation
|
||||
* and update timestamps. It is thread-safe and has a separate field for each
|
||||
* metric type.
|
||||
*
|
||||
* It is intended to only be used by SNTMetrics;
|
||||
*/
|
||||
@interface SNTMetricValue : NSObject
|
||||
/** Increment the counter by the step value, updating timestamps appropriately. */
|
||||
- (void)addInt64:(long long)step;
|
||||
|
||||
/** Set the Int64 value. */
|
||||
- (void)setInt64:(long long)value;
|
||||
|
||||
/** Set the double value. */
|
||||
- (void)setDouble:(double)value;
|
||||
|
||||
/** Set the string value. */
|
||||
- (void)setString:(NSString *)value;
|
||||
|
||||
/** Set the BOOL string value. */
|
||||
- (void)setBool:(BOOL)value;
|
||||
|
||||
/**
|
||||
* Clears the last update timestamp.
|
||||
*
|
||||
* This makes the metric value always emit the current timestamp as last update timestamp.
|
||||
*/
|
||||
- (void)clearLastUpdateTimestamp;
|
||||
|
||||
/** Getters */
|
||||
- (long long)getInt64Value;
|
||||
- (double)getDoubleValue;
|
||||
- (NSString *)getStringValue;
|
||||
@end
|
||||
|
||||
@implementation SNTMetricValue {
|
||||
/** The int64 value for the SNTMetricValue, if set. */
|
||||
long long _int64Value;
|
||||
|
||||
/** The double value for the SNTMetricValue, if set. */
|
||||
double _doubleValue;
|
||||
|
||||
/** The string value for the SNTMetricValue, if set. */
|
||||
NSString *_stringValue;
|
||||
|
||||
/** The boolean value for the SNTMetricValue, if set. */
|
||||
BOOL _boolValue;
|
||||
|
||||
/** The first time this cell got created in the current process. */
|
||||
NSDate *_creationTime;
|
||||
|
||||
/** The last time that the counter value was changed. */
|
||||
NSDate *_lastUpdate;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_creationTime = [NSDate date];
|
||||
_lastUpdate = _creationTime;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addInt64:(long long)step {
|
||||
@synchronized(self) {
|
||||
_int64Value += step;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInt64:(long long)value {
|
||||
@synchronized(self) {
|
||||
_int64Value = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (long long)getInt64Value {
|
||||
@synchronized(self) {
|
||||
return _int64Value;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDouble:(double)value {
|
||||
@synchronized(self) {
|
||||
_doubleValue = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (double)getDoubleValue {
|
||||
@synchronized(self) {
|
||||
return _doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setString:(NSString *)value {
|
||||
@synchronized(self) {
|
||||
_stringValue = [value copy];
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getStringValue {
|
||||
@synchronized(self) {
|
||||
return [_stringValue copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBool:(BOOL)value {
|
||||
@synchronized(self) {
|
||||
_boolValue = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)getBoolValue {
|
||||
@synchronized(self) {
|
||||
return _boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearLastUpdateTimestamp {
|
||||
@synchronized(self) {
|
||||
_lastUpdate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDate *)getLastUpdatedTimestamp {
|
||||
NSDate *updated = nil;
|
||||
@synchronized(self) {
|
||||
updated = [_lastUpdate copy];
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
- (NSDate *)getCreatedTimestamp {
|
||||
NSDate *created = nil;
|
||||
@synchronized(self) {
|
||||
created = [_creationTime copy];
|
||||
}
|
||||
return created;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetric {
|
||||
@private
|
||||
/** Fully qualified metric name e.g. /ops/security/santa. */
|
||||
NSString *_name;
|
||||
/** A help text for the metric to be exported to be exported. **/
|
||||
NSString *_help;
|
||||
|
||||
/** Sorted list of the fieldNames **/
|
||||
NSArray<NSString *> *_fieldNames;
|
||||
/** Mapping of field values to actual metric values (e.g. metric /proc/cpu_usage @"mode"=@"user"
|
||||
* -> 0.89 */
|
||||
NSMutableDictionary<NSArray<NSString *> *, SNTMetricValue *> *_metricsForFieldValues;
|
||||
/** the type of metric this is e.g. counter, gauge etc. **/
|
||||
SNTMetricType _type;
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)help
|
||||
type:(SNTMetricType)type {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = [name copy];
|
||||
_help = [help copy];
|
||||
_fieldNames = [fieldNames copy];
|
||||
_metricsForFieldValues = [[NSMutableDictionary alloc] init];
|
||||
_type = type;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)name {
|
||||
return _name;
|
||||
}
|
||||
|
||||
- (BOOL)hasSameSchemaAsMetric:(SNTMetric *)other {
|
||||
if (![other isKindOfClass:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
return [_name isEqualToString:other->_name] && [_help isEqualToString:other->_help] &&
|
||||
[_fieldNames isEqualTo:other->_fieldNames] && _type == other->_type;
|
||||
}
|
||||
|
||||
/** Retrieves the SNTMetricValue for a given field value.
|
||||
Creates a new SNTMetricValue if none is present. */
|
||||
- (SNTMetricValue *)metricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
NSParameterAssert(fieldValues.count == _fieldNames.count);
|
||||
SNTMetricValue *metricValue = nil;
|
||||
@synchronized(self) {
|
||||
metricValue = _metricsForFieldValues[fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
// Deep copy to prevent mutations to the keys we store in the dictionary.
|
||||
fieldValues = [fieldValues copy];
|
||||
metricValue = [[SNTMetricValue alloc] init];
|
||||
_metricsForFieldValues[fieldValues] = metricValue;
|
||||
}
|
||||
}
|
||||
|
||||
return metricValue;
|
||||
}
|
||||
|
||||
- (NSDictionary *)encodeMetricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = _metricsForFieldValues[fieldValues];
|
||||
|
||||
NSMutableDictionary *fieldDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
fieldDict[@"created"] = [metricValue getCreatedTimestamp];
|
||||
fieldDict[@"last_updated"] = [metricValue getLastUpdatedTimestamp];
|
||||
fieldDict[@"value"] = [fieldValues componentsJoinedByString:@","];
|
||||
|
||||
switch (_type) {
|
||||
case SNTMetricTypeConstantBool:
|
||||
case SNTMetricTypeGaugeBool:
|
||||
fieldDict[@"data"] = [NSNumber numberWithBool:[metricValue getBoolValue]];
|
||||
break;
|
||||
case SNTMetricTypeConstantInt64:
|
||||
case SNTMetricTypeCounter:
|
||||
case SNTMetricTypeGaugeInt64:
|
||||
fieldDict[@"data"] = [NSNumber numberWithLongLong:[metricValue getInt64Value]];
|
||||
break;
|
||||
case SNTMetricTypeConstantDouble:
|
||||
case SNTMetricTypeGaugeDouble:
|
||||
fieldDict[@"data"] = [NSNumber numberWithDouble:[metricValue getDoubleValue]];
|
||||
break;
|
||||
case SNTMetricTypeConstantString:
|
||||
case SNTMetricTypeGaugeString: fieldDict[@"data"] = [metricValue getStringValue]; break;
|
||||
default: break;
|
||||
}
|
||||
return fieldDict;
|
||||
}
|
||||
|
||||
- (NSDictionary *)export {
|
||||
NSMutableDictionary *metricDict = [NSMutableDictionary dictionaryWithCapacity:_fieldNames.count];
|
||||
metricDict[@"type"] = [NSNumber numberWithInt:(int)_type];
|
||||
metricDict[@"fields"] = [[NSMutableDictionary alloc] init];
|
||||
metricDict[@"description"] = [_help copy];
|
||||
|
||||
if (_fieldNames.count == 0) {
|
||||
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
|
||||
} else {
|
||||
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
|
||||
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
|
||||
}
|
||||
metricDict[@"fields"][[_fieldNames componentsJoinedByString:@","]] = fieldVals;
|
||||
}
|
||||
return metricDict;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricCounter
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeCounter];
|
||||
}
|
||||
|
||||
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return;
|
||||
}
|
||||
[metricValue addInt64:step];
|
||||
}
|
||||
|
||||
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
[self incrementBy:1 forFieldValues:fieldValues];
|
||||
}
|
||||
|
||||
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getInt64Value];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricInt64Gauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeGaugeInt64];
|
||||
}
|
||||
|
||||
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setInt64:value];
|
||||
}
|
||||
|
||||
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getInt64Value];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricDoubleGauge
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:text
|
||||
type:SNTMetricTypeGaugeDouble];
|
||||
}
|
||||
|
||||
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setDouble:value];
|
||||
}
|
||||
|
||||
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getDoubleValue];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricStringGauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:text
|
||||
type:SNTMetricTypeGaugeString];
|
||||
}
|
||||
|
||||
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setString:value];
|
||||
}
|
||||
|
||||
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [metricValue getStringValue];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricBooleanGauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeGaugeBool];
|
||||
}
|
||||
|
||||
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setBool:value];
|
||||
}
|
||||
|
||||
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [metricValue getBoolValue];
|
||||
}
|
||||
@end
|
||||
|
||||
/**
|
||||
* SNTMetricSet is the top level container for all metrics and metrics value
|
||||
* its is abstracted from specific implementations but is close to Google's
|
||||
* Monarch and Prometheus formats.
|
||||
*/
|
||||
@implementation SNTMetricSet {
|
||||
@private
|
||||
/** Labels that are used to identify the entity to that all metrics apply to. */
|
||||
NSMutableDictionary<NSString *, NSString *> *_rootLabels;
|
||||
/** Registered metrics keyed by name */
|
||||
NSMutableDictionary<NSString *, SNTMetric *> *_metrics;
|
||||
|
||||
/** Callbacks to update metric values before exporting metrics */
|
||||
NSMutableArray<void (^)(void)> *_callbacks;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static SNTMetricSet *sharedMetrics;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedMetrics = [[SNTMetricSet alloc] init];
|
||||
});
|
||||
|
||||
return sharedMetrics;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_rootLabels = [[NSMutableDictionary alloc] init];
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
_callbacks = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_rootLabels = [[NSMutableDictionary alloc] init];
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
_callbacks = [[NSMutableArray alloc] init];
|
||||
|
||||
_rootLabels[@"hostname"] = [hostname copy];
|
||||
_rootLabels[@"username"] = [username copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
- (void)addRootLabel:(NSString *)label value:(NSString *)value {
|
||||
@synchronized(self) {
|
||||
_rootLabels[label] = value;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeRootLabel:(NSString *)label {
|
||||
@synchronized(self) {
|
||||
[_rootLabels removeObjectForKey:label];
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTMetric *)registerMetric:(nonnull SNTMetric *)metric {
|
||||
@synchronized(self) {
|
||||
SNTMetric *oldMetric = _metrics[[metric name]];
|
||||
if ([oldMetric hasSameSchemaAsMetric:metric]) {
|
||||
return oldMetric;
|
||||
}
|
||||
NSAssert(!oldMetric, @"metric registered twice: %@", metric.name);
|
||||
_metrics[metric.name] = metric;
|
||||
}
|
||||
return metric;
|
||||
}
|
||||
|
||||
- (void)registerCallback:(void (^)(void))callback {
|
||||
@synchronized(self) {
|
||||
[_callbacks addObject:callback];
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTMetricCounter *)counterWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricCounter *c = [[SNTMetricCounter alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
return (SNTMetricCounter *)[self registerMetric:c];
|
||||
}
|
||||
|
||||
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricInt64Gauge *g = [[SNTMetricInt64Gauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
return (SNTMetricInt64Gauge *)[self registerMetric:g];
|
||||
}
|
||||
|
||||
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricDoubleGauge *g = [[SNTMetricDoubleGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
return (SNTMetricDoubleGauge *)[self registerMetric:g];
|
||||
}
|
||||
|
||||
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricStringGauge *s = [[SNTMetricStringGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
return (SNTMetricStringGauge *)[self registerMetric:s];
|
||||
}
|
||||
|
||||
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricBooleanGauge *b = [[SNTMetricBooleanGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
return (SNTMetricBooleanGauge *)[self registerMetric:b];
|
||||
}
|
||||
|
||||
- (void)addConstantStringWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(NSString *)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantString];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setString:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
- (void)addConstantIntegerWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(long long)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantInt64];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setInt64:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
- (void)addConstantBooleanWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(BOOL)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantBool];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setBool:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
/** Export current state of the SNTMetricSet as an NSDictionary. */
|
||||
- (NSDictionary *)export {
|
||||
NSDictionary *exported = nil;
|
||||
|
||||
// Invoke callbacks to ensure metrics are up to date.
|
||||
for (void (^cb)(void) in _callbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
NSMutableDictionary *exportDict = [[NSMutableDictionary alloc] init];
|
||||
exportDict[@"root_labels"] = [_rootLabels copy];
|
||||
exportDict[@"metrics"] = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// TODO(markowsky) Sort the metrics so we always get the same output.
|
||||
for (NSString *metricName in _metrics) {
|
||||
exportDict[@"metrics"][metricName] = [_metrics[metricName] export];
|
||||
}
|
||||
|
||||
exported = [NSDictionary dictionaryWithDictionary:exportDict];
|
||||
}
|
||||
return exported;
|
||||
}
|
||||
|
||||
// Returns a human readble string from an SNTMetricFormat type
|
||||
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format) {
|
||||
switch (format) {
|
||||
case SNTMetricFormatTypeRawJSON: return @"rawjson";
|
||||
case SNTMetricFormatTypeMonarchJSON: return @"monarchjson";
|
||||
default: return @"Unknown Metric 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;
|
||||
}
|
||||
|
||||
for (NSString *metricName in mutableMetrics[@"metrics"]) {
|
||||
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];
|
||||
|
||||
for (NSString *field in metric[@"fields"]) {
|
||||
NSMutableArray<NSMutableDictionary *> *values = metric[@"fields"][field];
|
||||
|
||||
[values enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
|
||||
values[index][@"created"] = [formatter stringFromDate:values[index][@"created"]];
|
||||
values[index][@"last_updated"] = [formatter stringFromDate:values[index][@"last_updated"]];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
return mutableMetrics;
|
||||
}
|
||||
|
||||
@end
|
||||
706
Source/common/SNTMetricSetTest.m
Normal file
706
Source/common/SNTMetricSetTest.m
Normal file
@@ -0,0 +1,706 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTMetricSet.h"
|
||||
|
||||
@interface SNTMetricCounterTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricGaugeInt64Test : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricDoubleGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricBooleanGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricStringGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricSetTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricSetHelperFunctionsTest : XCTestCase
|
||||
@end
|
||||
|
||||
// Stub out NSDate's date method
|
||||
@implementation NSDate (custom)
|
||||
|
||||
+ (instancetype)date {
|
||||
NSDateFormatter *formatter = NSDateFormatter.new;
|
||||
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
|
||||
return [formatter dateFromString:@"2021-08-05 13:00:10+0000"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricCounterTest
|
||||
- (void)testSimpleCounter {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of exec events broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(c, @"Expected returned SNTMetricCounter to not be nil");
|
||||
[c incrementForFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(1, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremented by 1");
|
||||
[c incrementBy:3 forFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(4, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremented by 3");
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of exec events broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(c);
|
||||
[c incrementForFieldValues:@[ @"certificate" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"description" : @"Count of exec events broken out by rule type.",
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[ @{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([c export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *a = [metricSet counterWithName:@"/santa/counter"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test counter."];
|
||||
|
||||
SNTMetricCounter *b = [metricSet counterWithName:@"/santa/counter"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test counter."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new counter returned.");
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricBooleanGaugeTest
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
XCTAssertNotNil(b);
|
||||
[b set:true forFieldValues:@[]];
|
||||
XCTAssertTrue([b getBoolValueForFieldValues:@[]]);
|
||||
[b set:false forFieldValues:@[]];
|
||||
XCTAssertFalse([b getBoolValueForFieldValues:@[]]);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
XCTAssertNotNil(b);
|
||||
[b set:true forFieldValues:@[]];
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeBool],
|
||||
@"description" : @"Is the daemon connected.",
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:true]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
NSDictionary *output = [b export];
|
||||
XCTAssertEqualObjects(output, expected);
|
||||
}
|
||||
|
||||
- (void)testAddingBooleanWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *a = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new boolean gauge returned.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricGaugeInt64Test
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *g =
|
||||
[metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of rules broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
|
||||
// set from zero
|
||||
[g set:250 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
|
||||
// Increase the gauge
|
||||
[g set:500 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(500, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// Decrease after increase
|
||||
[g set:100 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(100, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// Increase after decrease
|
||||
[g set:750 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(750, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// TODO: export the tree to JSON and confirm the structure is correct.
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *g =
|
||||
[metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of rules broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
|
||||
// set from zero
|
||||
[g set:250 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"description" : @"Count of rules broken out by rule type.",
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[ @{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:250]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([g export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *a = [metricSet int64GaugeWithName:@"/santa/int64gauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
SNTMetricInt64Gauge *b = [metricSet int64GaugeWithName:@"/santa/int64gauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricDoubleGaugeTest
|
||||
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
|
||||
fieldNames:@[ @"mode" ]
|
||||
helpText:@"CPU time consumed by this process."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
|
||||
// set from zero
|
||||
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.45, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
|
||||
// Increase the gauge
|
||||
[g set:(double)0.90 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.90, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
// Decrease after increase
|
||||
[g set:0.71 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.71, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
// Increase after decrease
|
||||
[g set:0.75 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.75, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
|
||||
fieldNames:@[ @"mode" ]
|
||||
helpText:@"CPU time consumed by this process."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
|
||||
// set from zero
|
||||
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
|
||||
[g set:(double)0.90 forFieldValues:@[ @"system" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeDouble],
|
||||
@"description" : @"CPU time consumed by this process.",
|
||||
@"fields" : @{
|
||||
@"mode" : @[
|
||||
@{
|
||||
@"value" : @"user",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithDouble:0.45]
|
||||
},
|
||||
@{
|
||||
@"value" : @"system",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithDouble:0.90]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
XCTAssertEqualObjects([g export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *a = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
SNTMetricDoubleGauge *b = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricStringGaugeTest
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
|
||||
fieldNames:@[]
|
||||
helpText:@"String description of the mode."];
|
||||
|
||||
XCTAssertNotNil(s);
|
||||
[s set:@"testValue" forFieldValues:@[]];
|
||||
XCTAssertEqualObjects([s getStringValueForFieldValues:@[]], @"testValue");
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
|
||||
fieldNames:@[]
|
||||
helpText:@"String description of the mode."];
|
||||
|
||||
XCTAssertNotNil(s);
|
||||
[s set:@"testValue" forFieldValues:@[]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeString],
|
||||
@"description" : @"String description of the mode.",
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"testValue"
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([s export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *a = [metricSet stringGaugeWithName:@"/santa/stringgauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
SNTMetricStringGauge *b = [metricSet stringGaugeWithName:@"/santa/stringgauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricSetTest
|
||||
- (void)testRootLabels {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addRootLabel:@"hostname" value:@"localhost"];
|
||||
|
||||
NSDictionary *expected = @{@"root_labels" : @{@"hostname" : @"localhost"}, @"metrics" : @{}};
|
||||
|
||||
XCTAssertEqualObjects(expected, [metricSet export]);
|
||||
|
||||
// ensure that adding a rootLabel with the same name overwrites.
|
||||
expected = @{@"root_labels" : @{@"hostname" : @"localhost2"}, @"metrics" : @{}};
|
||||
[metricSet addRootLabel:@"hostname" value:@"localhost2"];
|
||||
|
||||
XCTAssertEqualObjects(expected, [metricSet export],
|
||||
@"failed to overwrite rootLabel with second call to addRootLabel");
|
||||
|
||||
// ensure that removing a rootLabelWorks
|
||||
expected = @{@"root_labels" : @{}, @"metrics" : @{}};
|
||||
[metricSet removeRootLabel:@"hostname"];
|
||||
}
|
||||
|
||||
- (void)testDoubleRegisteringIncompatibleMetricsFails {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c = [metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"field" ]
|
||||
helpText:@"lorem ipsum"];
|
||||
|
||||
XCTAssertNotNil(c);
|
||||
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"incompatible" ]
|
||||
helpText:@"A little help text"],
|
||||
@"Should raise error for incompatible field names");
|
||||
|
||||
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"result" ]
|
||||
helpText:@"INCOMPATIBLE"],
|
||||
@"Should raise error for incompatible help text");
|
||||
}
|
||||
|
||||
- (void)testRegisterCallback {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
// Register a callback metric which increments by one before export
|
||||
SNTMetricInt64Gauge *gauge = [metricSet int64GaugeWithName:@"/foo/bar"
|
||||
fieldNames:@[]
|
||||
helpText:@"Number of callbacks done"];
|
||||
__block int count = 0;
|
||||
[metricSet registerCallback:^(void) {
|
||||
count++;
|
||||
[gauge set:count forFieldValues:@[]];
|
||||
}];
|
||||
|
||||
// ensure the callback is called.
|
||||
[metricSet export];
|
||||
|
||||
XCTAssertEqual([gauge getGaugeValueForFieldValues:@[]], 1);
|
||||
}
|
||||
|
||||
- (void)testAddConstantBool {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addConstantBooleanWithName:@"/tautology"
|
||||
helpText:@"The first rule of tautology club is the first rule"
|
||||
value:YES];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/tautology" : @{
|
||||
@"description" : @"The first rule of tautology club is the first rule",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:true]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testAddConstantString {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
|
||||
[metricSet addConstantStringWithName:@"/build/label"
|
||||
helpText:@"Build label for the binary"
|
||||
value:@"20210806.0.1"];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/build/label" : @{
|
||||
@"description" : @"Build label for the binary",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"20210806.0.1"
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testAddConstantInt {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addConstantIntegerWithName:@"/deep/thought/answer"
|
||||
helpText:@"Life, the universe, and everything"
|
||||
value:42];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/deep/thought/answer" : @{
|
||||
@"description" : @"Life, the universe, and everything",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithLongLong:42]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
|
||||
username:@"testUser"];
|
||||
|
||||
// Add constants
|
||||
[metricSet addConstantStringWithName:@"/build/label"
|
||||
helpText:@"Software version running."
|
||||
value:@"20210809.0.1"];
|
||||
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
|
||||
helpText:@"Is santad using the endpoint security framework."
|
||||
value:TRUE];
|
||||
[metricSet
|
||||
addConstantIntegerWithName:@"/proc/birth_timestamp"
|
||||
helpText:@"Start time of this santad instance, in microseconds since epoch"
|
||||
value:(long long)(0x12345668910)];
|
||||
// Add Metrics
|
||||
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of events on the host"];
|
||||
|
||||
[c incrementForFieldValues:@[ @"binary" ]];
|
||||
[c incrementBy:2 forFieldValues:@[ @"certificate" ]];
|
||||
|
||||
SNTMetricInt64Gauge *g = [metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Number of rules."];
|
||||
|
||||
[g set:1 forFieldValues:@[ @"binary" ]];
|
||||
[g set:3 forFieldValues:@[ @"certificate" ]];
|
||||
|
||||
// Add Metrics with callback
|
||||
SNTMetricInt64Gauge *virtualMemoryGauge =
|
||||
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
|
||||
fieldNames:@[]
|
||||
helpText:@"The virtual memory size of this process."];
|
||||
|
||||
SNTMetricInt64Gauge *residentMemoryGauge =
|
||||
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
|
||||
fieldNames:@[]
|
||||
helpText:@"The resident set size of this process."];
|
||||
|
||||
[metricSet registerCallback:^(void) {
|
||||
[virtualMemoryGauge set:987654321 forFieldValues:@[]];
|
||||
[residentMemoryGauge set:123456789 forFieldValues:@[]];
|
||||
}];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"},
|
||||
@"metrics" : @{
|
||||
@"/build/label" : @{
|
||||
@"description" : @"Software version running.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"20210809.0.1"
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/santa/events" : @{
|
||||
@"description" : @"Count of events on the host",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[
|
||||
@{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
@{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:2],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@"/santa/rules" : @{
|
||||
@"description" : @"Number of rules.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[
|
||||
@{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
@{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:3],
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
@"/santa/using_endpoint_security_framework" : @{
|
||||
@"description" : @"Is santad using the endpoint security framework.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:YES]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/proc/birth_timestamp" : @{
|
||||
@"description" : @"Start time of this santad instance, in microseconds since epoch",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithLong:1250999830800]
|
||||
} ]
|
||||
},
|
||||
},
|
||||
@"/proc/memory/virtual_size" : @{
|
||||
@"description" : @"The virtual memory size of this process.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:987654321]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/proc/memory/resident_size" : @{
|
||||
@"description" : @"The resident set size of this process.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:123456789]
|
||||
} ]
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export], expected);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricSetHelperFunctionsTest
|
||||
- (void)testMakeMetricString {
|
||||
NSArray<NSDictionary *> *tests = @[
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeUnknown],
|
||||
@"expected" : @"SNTMetricTypeUnknown 0"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
|
||||
@"expected" : @"SNTMetricTypeConstantBool"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
|
||||
@"expected" : @"SNTMetricTypeConstantString"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
|
||||
@"expected" : @"SNTMetricTypeConstantInt64"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
|
||||
@"expected" : @"SNTMetricTypeConstantDouble"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
|
||||
@"expected" : @"SNTMetricTypeGaugeBool"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
|
||||
@"expected" : @"SNTMetricTypeGaugeString"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
|
||||
@"expected" : @"SNTMetricTypeGaugeInt64"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
|
||||
@"expected" : @"SNTMetricTypeGaugeDouble"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
|
||||
@"expected" : @"SNTMetricTypeCounter"
|
||||
}
|
||||
];
|
||||
|
||||
for (NSDictionary *test in tests) {
|
||||
NSString *output = SNTMetricMakeStringFromMetricType([test[@"input"] integerValue]);
|
||||
XCTAssertEqualObjects(test[@"expected"], output, @"expected %@ got %@", test[@"expected"],
|
||||
output);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testEnsureMetricsWithMultipleFieldNamesSerializeOnce {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
|
||||
username:@"testUser"];
|
||||
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"client", @"event_type" ]
|
||||
helpText:@"Count of events on the host for a given ES client"];
|
||||
[c incrementBy:1 forFieldValues:@[ @"device_manager", @"auth_mount" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/santa/events" : @{
|
||||
@"description" : @"Count of events on the host for a given ES client",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"client,event_type" : @[
|
||||
@{
|
||||
@"value" : @"device_manager,auth_mount",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
NSDictionary *got = [metricSet export][@"metrics"];
|
||||
XCTAssertEqualObjects(expected, got, @"metrics do not match expected");
|
||||
}
|
||||
@end
|
||||
@@ -14,52 +14,45 @@
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
#include "Source/common/SNTLogging.h"
|
||||
|
||||
#else
|
||||
|
||||
#include <mutex>
|
||||
#include <string.h>
|
||||
|
||||
#define LOGD(format, ...) // NOP
|
||||
#define LOGE(format, ...) // NOP
|
||||
#include <mutex>
|
||||
|
||||
#define LOGD(format, ...) // NOP
|
||||
#define LOGE(format, ...) // NOP
|
||||
|
||||
#define lck_rw_lock_shared(l) pthread_rwlock_rdlock(&l)
|
||||
#define lck_rw_unlock_shared(l) pthread_rwlock_unlock(&l)
|
||||
#define lck_rw_lock_exclusive(l) pthread_rwlock_wrlock(&l)
|
||||
#define lck_rw_unlock_exclusive(l) pthread_rwlock_unlock(&l)
|
||||
|
||||
#define lck_rw_lock_shared_to_exclusive(l) ({ pthread_rwlock_unlock(&l); false; })
|
||||
#define lck_rw_lock_exclusive_to_shared(l) ({ pthread_rwlock_unlock(&l); pthread_rwlock_rdlock(&l); })
|
||||
#define lck_rw_lock_shared_to_exclusive(l) \
|
||||
({ \
|
||||
pthread_rwlock_unlock(&l); \
|
||||
false; \
|
||||
})
|
||||
#define lck_rw_lock_exclusive_to_shared(l) \
|
||||
({ \
|
||||
pthread_rwlock_unlock(&l); \
|
||||
pthread_rwlock_rdlock(&l); \
|
||||
})
|
||||
|
||||
#define lck_mtx_lock(l) l->lock()
|
||||
#define lck_mtx_unlock(l) l->unlock()
|
||||
#endif // KERNEL
|
||||
|
||||
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
max_nodes_ = max_nodes;
|
||||
|
||||
#ifdef KERNEL
|
||||
spt_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
spt_lock_grp_ = lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
|
||||
spt_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
spt_add_lock_ = lck_mtx_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
#else
|
||||
pthread_rwlock_init(&spt_lock_, nullptr);
|
||||
spt_add_lock_ = new std::mutex;
|
||||
#endif
|
||||
}
|
||||
|
||||
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could overwrite whole
|
||||
// branches of another. HasPrefix is still free to read the tree, until AddPrefix needs to
|
||||
// modify it.
|
||||
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could
|
||||
// overwrite whole branches of another. HasPrefix is still free to read the
|
||||
// tree, until AddPrefix needs to modify it.
|
||||
lck_mtx_lock(spt_add_lock_);
|
||||
|
||||
// Don't allow an empty prefix.
|
||||
@@ -74,13 +67,13 @@ IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
// If there is a node in the path that is considered a prefix, stop adding.
|
||||
// For our purposes we only care about the shortest path that matches.
|
||||
if (node->isPrefix) break;
|
||||
|
||||
// Only process a byte at a time.
|
||||
uint8_t value = prefix[i];
|
||||
uint8_t value = (uint8_t)prefix[i];
|
||||
|
||||
// Create the child if it does not exist.
|
||||
if (!node->children[value]) {
|
||||
@@ -103,7 +96,7 @@ IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
|
||||
// Create the rest of the prefix.
|
||||
while (i < len) {
|
||||
value = prefix[i++];
|
||||
value = (uint8_t)prefix[i++];
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
node->children[value] = new_node;
|
||||
@@ -159,7 +152,8 @@ bool SNTPrefixTree::HasPrefix(const char *string) {
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
|
||||
// A well formed tree will always break this loop. Even if string doesn't terminate.
|
||||
// A well formed tree will always break this loop. Even if string doesn't
|
||||
// terminate.
|
||||
const char *p = string;
|
||||
while (*p) {
|
||||
// Only process a byte at a time.
|
||||
@@ -193,8 +187,8 @@ void SNTPrefixTree::Reset() {
|
||||
void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
|
||||
if (!target) return;
|
||||
|
||||
// For deep trees, a recursive approach will generate too many stack frames. Make a "stack"
|
||||
// and walk the tree.
|
||||
// For deep trees, a recursive approach will generate too many stack frames.
|
||||
// Make a "stack" and walk the tree.
|
||||
auto stack = new SantaPrefixNode *[node_count_ + 1];
|
||||
if (!stack) {
|
||||
LOGE("Unable to prune tree!");
|
||||
@@ -206,7 +200,8 @@ void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
|
||||
// Seed the "stack" with a starting node.
|
||||
stack[count++] = target;
|
||||
|
||||
// Start at the target node and walk the tree to find and delete all the sub-nodes.
|
||||
// Start at the target node and walk the tree to find and delete all the
|
||||
// sub-nodes.
|
||||
while (count) {
|
||||
auto node = stack[--count];
|
||||
|
||||
@@ -228,32 +223,5 @@ SNTPrefixTree::~SNTPrefixTree() {
|
||||
root_ = nullptr;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
|
||||
#ifdef KERNEL
|
||||
if (spt_lock_) {
|
||||
lck_rw_free(spt_lock_, spt_lock_grp_);
|
||||
spt_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_add_lock_) {
|
||||
lck_mtx_free(spt_add_lock_, spt_lock_grp_);
|
||||
spt_add_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_attr_) {
|
||||
lck_attr_free(spt_lock_attr_);
|
||||
spt_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_) {
|
||||
lck_grp_free(spt_lock_grp_);
|
||||
spt_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_attr_) {
|
||||
lck_grp_attr_free(spt_lock_grp_attr_);
|
||||
spt_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
#else
|
||||
pthread_rwlock_destroy(&spt_lock_);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
#else
|
||||
// Support for unit testing.
|
||||
#include <mutex>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#endif // KERNEL
|
||||
|
||||
#include <mutex>
|
||||
|
||||
///
|
||||
/// SantaPrefixTree is a simple prefix tree implementation.
|
||||
@@ -54,15 +51,17 @@ class SNTPrefixTree {
|
||||
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
|
||||
///
|
||||
/// The path for "/🤘" would look like this:
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4] -> children[0x98]
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4]
|
||||
/// -> children[0x98]
|
||||
///
|
||||
/// The path for "/dev" is:
|
||||
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
|
||||
///
|
||||
/// Lookups of children are O(1).
|
||||
///
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2 byte), would
|
||||
/// drastically decrease the memory footprint but would double required dereferences.
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2
|
||||
/// byte), would drastically decrease the memory footprint but would double
|
||||
/// required dereferences.
|
||||
///
|
||||
/// TODO(bur): Potentially convert this into a full on radix tree.
|
||||
///
|
||||
@@ -74,8 +73,8 @@ class SNTPrefixTree {
|
||||
|
||||
// PruneNode will remove the passed in node from the tree.
|
||||
// The passed in node and all subnodes will be deleted.
|
||||
// It is the caller's responsibility to reset the pointer to this node (held by the parent).
|
||||
// If the tree is in use grab the exclusive lock.
|
||||
// It is the caller's responsibility to reset the pointer to this node (held
|
||||
// by the parent). If the tree is in use grab the exclusive lock.
|
||||
void PruneNode(SantaPrefixNode *);
|
||||
|
||||
SantaPrefixNode *root_;
|
||||
@@ -85,19 +84,8 @@ class SNTPrefixTree {
|
||||
uint32_t max_nodes_;
|
||||
uint32_t node_count_;
|
||||
|
||||
#ifdef KERNEL
|
||||
lck_grp_t *spt_lock_grp_;
|
||||
lck_grp_attr_t *spt_lock_grp_attr_;
|
||||
lck_attr_t *spt_lock_attr_;
|
||||
lck_rw_t *spt_lock_;
|
||||
lck_mtx_t *spt_add_lock_;
|
||||
#else // KERNEL
|
||||
void *spt_lock_grp_;
|
||||
void *spt_lock_grp_attr_;
|
||||
void *spt_lock_attr_;
|
||||
pthread_rwlock_t spt_lock_;
|
||||
std::mutex *spt_add_lock_;
|
||||
#endif // KERNEL
|
||||
};
|
||||
|
||||
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */
|
||||
|
||||
@@ -45,26 +45,29 @@
|
||||
[UUIDs addObject:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
__block BOOL stop = NO;
|
||||
|
||||
// Create a bunch of background noise.
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
dispatch_apply(UINT64_MAX, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
t->HasPrefix([UUIDs[i % count] UTF8String]);
|
||||
});
|
||||
for (uint64_t i = 0; i < UINT64_MAX; ++i) {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
t->HasPrefix([UUIDs[i % count] UTF8String]);
|
||||
});
|
||||
if (stop) return;
|
||||
}
|
||||
});
|
||||
|
||||
// Fill up the tree.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
if (t->AddPrefix([UUIDs[i] UTF8String]) != kIOReturnSuccess) {
|
||||
XCTFail();
|
||||
}
|
||||
XCTAssertEqual(t->AddPrefix([UUIDs[i] UTF8String]), kIOReturnSuccess);
|
||||
});
|
||||
|
||||
// Make sure every leaf byte is found.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
if (!t->HasPrefix([UUIDs[i] UTF8String])) {
|
||||
XCTFail();
|
||||
}
|
||||
XCTAssertTrue(t->HasPrefix([UUIDs[i] UTF8String]));
|
||||
});
|
||||
|
||||
stop = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -19,12 +19,12 @@
|
||||
///
|
||||
/// Represents a Rule.
|
||||
///
|
||||
@interface SNTRule : NSObject<NSSecureCoding>
|
||||
@interface SNTRule : NSObject <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// The hash of the object this rule is for
|
||||
///
|
||||
@property(copy) NSString *shasum;
|
||||
@property(copy) NSString *identifier;
|
||||
|
||||
///
|
||||
/// The state of this rule
|
||||
@@ -50,19 +50,24 @@
|
||||
///
|
||||
/// Designated initializer.
|
||||
///
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp;
|
||||
|
||||
///
|
||||
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
|
||||
///
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg;
|
||||
|
||||
///
|
||||
/// Initialize with a dictionary received from a sync server.
|
||||
///
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dict;
|
||||
|
||||
///
|
||||
/// Sets timestamp of rule to the current time.
|
||||
|
||||
@@ -13,21 +13,22 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
|
||||
@interface SNTRule()
|
||||
@interface SNTRule ()
|
||||
@property(readwrite) NSUInteger timestamp;
|
||||
@end
|
||||
|
||||
@implementation SNTRule
|
||||
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp {
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_shasum = shasum;
|
||||
_identifier = identifier;
|
||||
_state = state;
|
||||
_type = type;
|
||||
_customMsg = customMsg;
|
||||
@@ -36,28 +37,78 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
self = [self initWithShasum:shasum
|
||||
state:state
|
||||
type:type
|
||||
customMsg:customMsg
|
||||
timestamp:0];
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
self = [self initWithIdentifier:identifier state:state type:type customMsg:customMsg timestamp:0];
|
||||
// Initialize timestamp to current time if rule is transitive.
|
||||
if (self && state == SNTRuleStateWhitelistTransitive) {
|
||||
if (self && state == SNTRuleStateAllowTransitive) {
|
||||
[self resetTimestamp];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Converts rule information downloaded from the server into a SNTRule. Because any information
|
||||
// not recorded by SNTRule is thrown away here, this method is also responsible for dealing with
|
||||
// the extra bundle rule information (bundle_hash & rule_count).
|
||||
- (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;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#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 ENCODE(obj, key) \
|
||||
if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
@@ -65,7 +116,7 @@
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.shasum, @"shasum");
|
||||
ENCODE(self.identifier, @"identifier");
|
||||
ENCODE(@(self.state), @"state");
|
||||
ENCODE(@(self.type), @"type");
|
||||
ENCODE(self.customMsg, @"custommsg");
|
||||
@@ -75,7 +126,7 @@
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_shasum = DECODE(NSString, @"shasum");
|
||||
_identifier = DECODE(NSString, @"identifier");
|
||||
_state = [DECODE(NSNumber, @"state") intValue];
|
||||
_type = [DECODE(NSNumber, @"type") intValue];
|
||||
_customMsg = DECODE(NSString, @"custommsg");
|
||||
@@ -92,24 +143,25 @@
|
||||
if (other == self) return YES;
|
||||
if (![other isKindOfClass:[SNTRule class]]) return NO;
|
||||
SNTRule *o = other;
|
||||
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
|
||||
return ([self.identifier isEqual:o.identifier] && self.state == o.state && self.type == o.type);
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
NSUInteger prime = 31;
|
||||
NSUInteger result = 1;
|
||||
result = prime * result + [self.shasum hash];
|
||||
result = prime * result + [self.identifier hash];
|
||||
result = prime * result + self.state;
|
||||
result = prime * result + self.type;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
|
||||
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
|
||||
return [NSString
|
||||
stringWithFormat:@"SNTRule: Identifier: %@, State: %ld, Type: %ld, Timestamp: %lu",
|
||||
self.identifier, self.state, self.type, (unsigned long)self.timestamp];
|
||||
}
|
||||
|
||||
# pragma mark Last-access Timestamp
|
||||
#pragma mark Last-access Timestamp
|
||||
|
||||
- (void)resetTimestamp {
|
||||
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];
|
||||
|
||||
116
Source/common/SNTRuleTest.m
Normal file
116
Source/common/SNTRuleTest.m
Normal file
@@ -0,0 +1,116 @@
|
||||
/// Copyright 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.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
@interface SNTRuleTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTRuleTest
|
||||
|
||||
- (void)testInitWithDictionaryValid {
|
||||
SNTRule *sut;
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"BINARY",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeBinary);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateAllow);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"sha256" : @"some-sort-of-identifier",
|
||||
@"policy" : @"BLOCKLIST",
|
||||
@"rule_type" : @"CERTIFICATE",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeCertificate);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateBlock);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"policy" : @"SILENT_BLOCKLIST",
|
||||
@"rule_type" : @"TEAMID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateSilentBlock);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"policy" : @"ALLOWLIST_COMPILER",
|
||||
@"rule_type" : @"BINARY",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeBinary);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateAllowCompiler);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"policy" : @"REMOVE",
|
||||
@"rule_type" : @"TEAMID",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateRemove);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"some-sort-of-identifier",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"TEAMID",
|
||||
@"custom_msg" : @"A custom block message",
|
||||
}];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.identifier, @"some-sort-of-identifier");
|
||||
XCTAssertEqual(sut.type, SNTRuleTypeTeamID);
|
||||
XCTAssertEqual(sut.state, SNTRuleStateAllow);
|
||||
XCTAssertEqualObjects(sut.customMsg, @"A custom block message");
|
||||
}
|
||||
|
||||
- (void)testInitWithDictionaryInvalid {
|
||||
SNTRule *sut;
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{}];
|
||||
XCTAssertNil(sut);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"an-identifier",
|
||||
}];
|
||||
XCTAssertNil(sut);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"an-identifier",
|
||||
@"policy" : @"OTHERPOLICY",
|
||||
@"rule_type" : @"BINARY",
|
||||
}];
|
||||
XCTAssertNil(sut);
|
||||
|
||||
sut = [[SNTRule alloc] initWithDictionary:@{
|
||||
@"identifier" : @"an-identifier",
|
||||
@"policy" : @"ALLOWLIST",
|
||||
@"rule_type" : @"OTHER_RULE_TYPE",
|
||||
}];
|
||||
XCTAssertNil(sut);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -19,7 +19,7 @@
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
///
|
||||
@interface SNTStoredEvent : NSObject<NSSecureCoding>
|
||||
@interface SNTStoredEvent : NSObject <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// An index for this event, randomly generated during initialization.
|
||||
@@ -95,6 +95,11 @@
|
||||
///
|
||||
@property NSArray *signingChain;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Team ID if present in the signature information.
|
||||
///
|
||||
@property NSString *teamID;
|
||||
|
||||
///
|
||||
/// The user who executed the binary.
|
||||
///
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
|
||||
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define ENCODE(obj, key) \
|
||||
if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
@@ -48,6 +49,7 @@
|
||||
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
|
||||
|
||||
ENCODE(self.signingChain, @"signingChain");
|
||||
ENCODE(self.teamID, @"teamID");
|
||||
|
||||
ENCODE(self.executingUser, @"executingUser");
|
||||
ENCODE(self.occurrenceDate, @"occurrenceDate");
|
||||
@@ -92,6 +94,7 @@
|
||||
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
|
||||
|
||||
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
|
||||
_teamID = DECODE(NSString, @"teamID");
|
||||
|
||||
_executingUser = DECODE(NSString, @"executingUser");
|
||||
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
|
||||
@@ -129,7 +132,7 @@
|
||||
|
||||
- (NSString *)description {
|
||||
return
|
||||
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
|
||||
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
/// Copyright 2016-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.
|
||||
@@ -12,11 +12,14 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
// clang-format off
|
||||
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define WEAKIFY(var) \
|
||||
__weak __typeof(var) Weak_##var = (var);
|
||||
#define WEAKIFY(var) __weak __typeof(var) Weak_##var = (var);
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -21,6 +21,7 @@ extern NSString *const kHostname;
|
||||
extern NSString *const kSantaVer;
|
||||
extern NSString *const kOSVer;
|
||||
extern NSString *const kOSBuild;
|
||||
extern NSString *const kModelIdentifier;
|
||||
extern NSString *const kPrimaryUser;
|
||||
extern NSString *const kRequestCleanSync;
|
||||
extern NSString *const kBatchSize;
|
||||
@@ -28,20 +29,29 @@ extern NSString *const kUploadLogsURL;
|
||||
extern NSString *const kClientMode;
|
||||
extern NSString *const kClientModeMonitor;
|
||||
extern NSString *const kClientModeLockdown;
|
||||
extern NSString *const kBlockUSBMount;
|
||||
extern NSString *const kRemountUSBMode;
|
||||
extern NSString *const kCleanSync;
|
||||
extern NSString *const kWhitelistRegex;
|
||||
extern NSString *const kBlacklistRegex;
|
||||
extern NSString *const kAllowedPathRegex;
|
||||
extern NSString *const kAllowedPathRegexDeprecated;
|
||||
extern NSString *const kBlockedPathRegex;
|
||||
extern NSString *const kBlockedPathRegexDeprecated;
|
||||
extern NSString *const kBinaryRuleCount;
|
||||
extern NSString *const kCertificateRuleCount;
|
||||
extern NSString *const kCompilerRuleCount;
|
||||
extern NSString *const kTransitiveRuleCount;
|
||||
extern NSString *const kTeamIDRuleCount;
|
||||
extern NSString *const kFullSyncInterval;
|
||||
extern NSString *const kFCMToken;
|
||||
extern NSString *const kFCMFullSyncInterval;
|
||||
extern NSString *const kFCMGlobalRuleSyncDeadline;
|
||||
extern NSString *const kEnableBundles;
|
||||
extern NSString *const kEnableBundles_OLD;
|
||||
extern NSString *const kEnableTransitiveWhitelisting;
|
||||
extern NSString *const kEnableTransitiveWhitelisting_OLD;
|
||||
extern NSString *const kEnableBundlesDeprecated;
|
||||
extern NSString *const kEnableTransitiveRules;
|
||||
extern NSString *const kEnableTransitiveRulesDeprecated;
|
||||
extern NSString *const kEnableTransitiveRulesSuperDeprecated;
|
||||
extern NSString *const kEnableAllEventUpload;
|
||||
extern NSString *const kDisableUnknownEventUpload;
|
||||
|
||||
extern NSString *const kEvents;
|
||||
extern NSString *const kFileSHA256;
|
||||
@@ -54,10 +64,12 @@ extern NSString *const kDecisionAllowUnknown;
|
||||
extern NSString *const kDecisionAllowBinary;
|
||||
extern NSString *const kDecisionAllowCertificate;
|
||||
extern NSString *const kDecisionAllowScope;
|
||||
extern NSString *const kDecisionAllowTeamID;
|
||||
extern NSString *const kDecisionBlockUnknown;
|
||||
extern NSString *const kDecisionBlockBinary;
|
||||
extern NSString *const kDecisionBlockCertificate;
|
||||
extern NSString *const kDecisionBlockScope;
|
||||
extern NSString *const kDecisionBlockTeamID;
|
||||
extern NSString *const kDecisionUnknown;
|
||||
extern NSString *const kDecisionBundleBinary;
|
||||
extern NSString *const kLoggedInUsers;
|
||||
@@ -81,6 +93,7 @@ extern NSString *const kCertOrg;
|
||||
extern NSString *const kCertOU;
|
||||
extern NSString *const kCertValidFrom;
|
||||
extern NSString *const kCertValidUntil;
|
||||
extern NSString *const kTeamID;
|
||||
extern NSString *const kQuarantineDataURL;
|
||||
extern NSString *const kQuarantineRefererURL;
|
||||
extern NSString *const kQuarantineTimestamp;
|
||||
@@ -89,15 +102,21 @@ extern NSString *const kEventUploadBundleBinaries;
|
||||
|
||||
extern NSString *const kRules;
|
||||
extern NSString *const kRuleSHA256;
|
||||
extern NSString *const kRuleIdentifier;
|
||||
extern NSString *const kRulePolicy;
|
||||
extern NSString *const kRulePolicyWhitelist;
|
||||
extern NSString *const kRulePolicyWhitelistCompiler;
|
||||
extern NSString *const kRulePolicyBlacklist;
|
||||
extern NSString *const kRulePolicySilentBlacklist;
|
||||
extern NSString *const kRulePolicyAllowlist;
|
||||
extern NSString *const kRulePolicyAllowlistDeprecated;
|
||||
extern NSString *const kRulePolicyAllowlistCompiler;
|
||||
extern NSString *const kRulePolicyAllowlistCompilerDeprecated;
|
||||
extern NSString *const kRulePolicyBlocklist;
|
||||
extern NSString *const kRulePolicyBlocklistDeprecated;
|
||||
extern NSString *const kRulePolicySilentBlocklist;
|
||||
extern NSString *const kRulePolicySilentBlocklistDeprecated;
|
||||
extern NSString *const kRulePolicyRemove;
|
||||
extern NSString *const kRuleType;
|
||||
extern NSString *const kRuleTypeBinary;
|
||||
extern NSString *const kRuleTypeCertificate;
|
||||
extern NSString *const kRuleTypeTeamID;
|
||||
extern NSString *const kRuleCustomMsg;
|
||||
extern NSString *const kCursor;
|
||||
|
||||
@@ -118,6 +137,5 @@ extern const NSUInteger kDefaultEventBatchSize;
|
||||
/// Are represented in seconds
|
||||
///
|
||||
extern const NSUInteger kDefaultFullSyncInterval;
|
||||
extern const NSUInteger kDefaultFCMFullSyncInterval;
|
||||
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;
|
||||
|
||||
extern const NSUInteger kDefaultPushNotificationsFullSyncInterval;
|
||||
extern const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline;
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
|
||||
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
|
||||
|
||||
@@ -21,29 +21,38 @@ NSString *const kHostname = @"hostname";
|
||||
NSString *const kSantaVer = @"santa_version";
|
||||
NSString *const kOSVer = @"os_version";
|
||||
NSString *const kOSBuild = @"os_build";
|
||||
NSString *const kModelIdentifier = @"model_identifier";
|
||||
NSString *const kPrimaryUser = @"primary_user";
|
||||
NSString *const kRequestCleanSync = @"request_clean_sync";
|
||||
NSString *const kBatchSize = @"batch_size";
|
||||
NSString *const kUploadLogsURL = @"upload_logs_url";
|
||||
NSString *const kClientMode = @"client_mode";
|
||||
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 kWhitelistRegex = @"whitelist_regex";
|
||||
NSString *const kBlacklistRegex = @"blacklist_regex";
|
||||
NSString *const kAllowedPathRegex = @"allowed_path_regex";
|
||||
NSString *const kAllowedPathRegexDeprecated = @"whitelist_regex";
|
||||
NSString *const kBlockedPathRegex = @"blocked_path_regex";
|
||||
NSString *const kBlockedPathRegexDeprecated = @"blacklist_regex";
|
||||
NSString *const kBinaryRuleCount = @"binary_rule_count";
|
||||
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 kFullSyncInterval = @"full_sync_interval";
|
||||
NSString *const kFCMToken = @"fcm_token";
|
||||
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
|
||||
NSString *const kFCMGlobalRuleSyncDeadline = @"fcm_global_rule_sync_deadline";
|
||||
|
||||
// NOTE: Both of the _OLD values will be removed at some indeterminate point in the future.
|
||||
NSString *const kEnableBundles = @"enable_bundles";
|
||||
NSString *const kEnableBundles_OLD = @"bundles_enabled";
|
||||
NSString *const kEnableTransitiveWhitelisting = @"enabled_transitive_whitelisting";
|
||||
NSString *const kEnableTransitiveWhitelisting_OLD = @"transitive_whitelisting_enabled";
|
||||
NSString *const kEnableBundlesDeprecated = @"bundles_enabled";
|
||||
NSString *const kEnableTransitiveRules = @"enable_transitive_rules";
|
||||
NSString *const kEnableTransitiveRulesDeprecated = @"enabled_transitive_whitelisting";
|
||||
NSString *const kEnableTransitiveRulesSuperDeprecated = @"transitive_whitelisting_enabled";
|
||||
NSString *const kEnableAllEventUpload = @"enable_all_event_upload";
|
||||
NSString *const kDisableUnknownEventUpload = @"disable_unknown_event_upload";
|
||||
|
||||
NSString *const kEvents = @"events";
|
||||
NSString *const kFileSHA256 = @"file_sha256";
|
||||
@@ -56,10 +65,12 @@ NSString *const kDecisionAllowUnknown = @"ALLOW_UNKNOWN";
|
||||
NSString *const kDecisionAllowBinary = @"ALLOW_BINARY";
|
||||
NSString *const kDecisionAllowCertificate = @"ALLOW_CERTIFICATE";
|
||||
NSString *const kDecisionAllowScope = @"ALLOW_SCOPE";
|
||||
NSString *const kDecisionAllowTeamID = @"ALLOW_TEAMID";
|
||||
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 kDecisionUnknown = @"UNKNOWN";
|
||||
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
|
||||
NSString *const kLoggedInUsers = @"logged_in_users";
|
||||
@@ -83,6 +94,7 @@ NSString *const kCertOrg = @"org";
|
||||
NSString *const kCertOU = @"ou";
|
||||
NSString *const kCertValidFrom = @"valid_from";
|
||||
NSString *const kCertValidUntil = @"valid_until";
|
||||
NSString *const kTeamID = @"team_id";
|
||||
NSString *const kQuarantineDataURL = @"quarantine_data_url";
|
||||
NSString *const kQuarantineRefererURL = @"quarantine_referer_url";
|
||||
NSString *const kQuarantineTimestamp = @"quarantine_timestamp";
|
||||
@@ -91,15 +103,21 @@ NSString *const kEventUploadBundleBinaries = @"event_upload_bundle_binaries";
|
||||
|
||||
NSString *const kRules = @"rules";
|
||||
NSString *const kRuleSHA256 = @"sha256";
|
||||
NSString *const kRuleIdentifier = @"identifier";
|
||||
NSString *const kRulePolicy = @"policy";
|
||||
NSString *const kRulePolicyWhitelist = @"WHITELIST";
|
||||
NSString *const kRulePolicyWhitelistCompiler = @"WHITELIST_COMPILER";
|
||||
NSString *const kRulePolicyBlacklist = @"BLACKLIST";
|
||||
NSString *const kRulePolicySilentBlacklist = @"SILENT_BLACKLIST";
|
||||
NSString *const kRulePolicyAllowlist = @"ALLOWLIST";
|
||||
NSString *const kRulePolicyAllowlistDeprecated = @"WHITELIST";
|
||||
NSString *const kRulePolicyAllowlistCompiler = @"ALLOWLIST_COMPILER";
|
||||
NSString *const kRulePolicyAllowlistCompilerDeprecated = @"WHITELIST_COMPILER";
|
||||
NSString *const kRulePolicyBlocklist = @"BLOCKLIST";
|
||||
NSString *const kRulePolicyBlocklistDeprecated = @"BLACKLIST";
|
||||
NSString *const kRulePolicySilentBlocklist = @"SILENT_BLOCKLIST";
|
||||
NSString *const kRulePolicySilentBlocklistDeprecated = @"SILENT_BLACKLIST";
|
||||
NSString *const kRulePolicyRemove = @"REMOVE";
|
||||
NSString *const kRuleType = @"rule_type";
|
||||
NSString *const kRuleTypeBinary = @"BINARY";
|
||||
NSString *const kRuleTypeCertificate = @"CERTIFICATE";
|
||||
NSString *const kRuleTypeTeamID = @"TEAMID";
|
||||
NSString *const kRuleCustomMsg = @"custom_msg";
|
||||
NSString *const kCursor = @"cursor";
|
||||
|
||||
@@ -112,5 +130,5 @@ NSString *const kLogSync = @"log_sync";
|
||||
|
||||
const NSUInteger kDefaultEventBatchSize = 50;
|
||||
const NSUInteger kDefaultFullSyncInterval = 600;
|
||||
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
|
||||
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;
|
||||
const NSUInteger kDefaultPushNotificationsFullSyncInterval = 14400;
|
||||
const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline = 600;
|
||||
@@ -49,4 +49,9 @@
|
||||
///
|
||||
+ (NSString *)longHostname;
|
||||
|
||||
///
|
||||
/// @return Model Identifier
|
||||
///
|
||||
+ (NSString *)modelIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -13,16 +13,20 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
@implementation SNTSystemInfo
|
||||
|
||||
+ (NSString *)serialNumber {
|
||||
io_service_t platformExpert = IOServiceGetMatchingService(
|
||||
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
io_service_t platformExpert =
|
||||
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
#pragma clang diagnostic pop
|
||||
if (!platformExpert) return nil;
|
||||
|
||||
NSString *serial = CFBridgingRelease(IORegistryEntryCreateCFProperty(
|
||||
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
|
||||
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
|
||||
|
||||
IOObjectRelease(platformExpert);
|
||||
|
||||
@@ -30,12 +34,15 @@
|
||||
}
|
||||
|
||||
+ (NSString *)hardwareUUID {
|
||||
io_service_t platformExpert = IOServiceGetMatchingService(
|
||||
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
io_service_t platformExpert =
|
||||
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
#pragma clang diagnostic pop
|
||||
if (!platformExpert) return nil;
|
||||
|
||||
NSString *uuid = CFBridgingRelease(IORegistryEntryCreateCFProperty(
|
||||
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
|
||||
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
|
||||
|
||||
IOObjectRelease(platformExpert);
|
||||
|
||||
@@ -60,11 +67,18 @@
|
||||
return @(hostname);
|
||||
}
|
||||
|
||||
+ (NSString *)modelIdentifier {
|
||||
char model[32];
|
||||
size_t len = 32;
|
||||
sysctlbyname("hw.model", model, &len, NULL, 0);
|
||||
return @(model);
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
+ (NSDictionary *)_systemVersionDictionary {
|
||||
return [NSDictionary
|
||||
dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
|
||||
return
|
||||
[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -42,7 +42,8 @@ typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNu
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
|
||||
|
||||
///
|
||||
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice know you are done with it.
|
||||
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice
|
||||
/// know you are done with it.
|
||||
///
|
||||
- (void)spindown;
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(hashBundleBinariesForEvent:reply:)
|
||||
argumentIndex:1
|
||||
ofReply:YES];
|
||||
forSelector:@selector(hashBundleBinariesForEvent:reply:)
|
||||
argumentIndex:1
|
||||
ofReply:YES];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
@protocol SNTDaemonControlXPC <SNTUnprivilegedDaemonControlXPC>
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
/// Cache ops
|
||||
///
|
||||
- (void)flushCache:(void (^)(BOOL))reply;
|
||||
|
||||
@@ -34,25 +34,28 @@
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
///
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)(void))reply;
|
||||
- (void)setXsrfToken:(NSString *)token 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)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
|
||||
- (void)setBlacklistPathRegex:(NSString *)pattern 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;
|
||||
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply;
|
||||
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
|
||||
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
- (void)setEnableAllEventUpload:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
- (void)setDisableUnknownEventUpload:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
|
||||
///
|
||||
/// Syncd Ops
|
||||
///
|
||||
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,13 +27,16 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
@implementation SNTXPCControlInterface
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
}
|
||||
return kBundleID;
|
||||
#ifdef SANTAADHOC
|
||||
// The mach service for an adhoc signed ES sysx uses the "endpoint-security" prefix instead of
|
||||
// the teamid. In Santa's case it will be endpoint-security.com.google.santa.daemon.xpc.
|
||||
return [NSString stringWithFormat:@"endpoint-security.%@.xpc", kBundleID];
|
||||
#else
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSString *)systemExtensionID {
|
||||
@@ -42,14 +45,14 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(databaseEventsPending:)
|
||||
argumentIndex:0
|
||||
ofReply:YES];
|
||||
forSelector:@selector(databaseEventsPending:)
|
||||
argumentIndex:0
|
||||
ofReply:YES];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTRule class], nil]
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
|
||||
50
Source/common/SNTXPCMetricServiceInterface.h
Normal file
50
Source/common/SNTXPCMetricServiceInterface.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
/// Protocol implemented by the metric service and utilized by santad
|
||||
/// exporting metrics to a monitoring system.
|
||||
@protocol SNTMetricServiceXPC
|
||||
|
||||
///
|
||||
/// @param metrics The current metric/counter values serialized to an NSDictionary.
|
||||
///
|
||||
- (void)exportForMonitoring:(NSDictionary *)metrics;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTXPCMetricServiceInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTMetricServiceXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up
|
||||
/// before returning.
|
||||
///
|
||||
+ (NSXPCInterface *)metricServiceInterface;
|
||||
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with santametricservice.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
42
Source/common/SNTXPCMetricServiceInterface.m
Normal file
42
Source/common/SNTXPCMetricServiceInterface.m
Normal file
@@ -0,0 +1,42 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCMetricServiceInterface.h"
|
||||
|
||||
@implementation SNTXPCMetricServiceInterface
|
||||
|
||||
+ (NSXPCInterface *)metricServiceInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTMetricServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSDictionary class], [NSArray class], [NSNumber class],
|
||||
[NSString class], [NSDate class], nil]
|
||||
forSelector:@selector(exportForMonitoring:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
return @"com.google.santa.metricservice";
|
||||
}
|
||||
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:NO];
|
||||
c.remoteInterface = [self metricServiceInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -18,10 +18,12 @@
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
@class SNTDeviceEvent;
|
||||
|
||||
/// Protocol implemented by SantaGUI and utilized by santad
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
83
Source/common/SNTXPCSyncServiceInterface.h
Normal file
83
Source/common/SNTXPCSyncServiceInterface.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/// Copyright 2020 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
///
|
||||
/// Protocol implemented by syncservice and utilized by daemon and ctl for communication with a
|
||||
/// sync server.
|
||||
///
|
||||
@protocol SNTSyncServiceXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
|
||||
// The syncservice regularly syncs with a configured sync server. Use this method to sync out of
|
||||
// band. The syncservice ensures syncs do not run concurrently.
|
||||
//
|
||||
// Pass an NSXPCListenerEndpoint whose associated NSXPCListener exports an object that implements
|
||||
// the SNTSyncServiceLogReceiverXPC protocol. The caller will receive sync logs over this listener.
|
||||
// This is required.
|
||||
//
|
||||
// Syncs are enqueued in order and executed serially. kMaxEnqueuedSyncs limits the number of syncs
|
||||
// in the queue. If the queue is full calls to this method will be dropped and
|
||||
// SNTSyncStatusTypeTooManySyncsInProgress will be passed into the reply block.
|
||||
//
|
||||
// Pass true to isClean to perform a clean sync, defaults to false.
|
||||
//
|
||||
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
|
||||
isClean:(BOOL)cleanSync
|
||||
reply:(void (^)(SNTSyncStatusType))reply;
|
||||
|
||||
// Spindown the syncservice. The syncservice will not automatically start back up.
|
||||
// A new connection to the syncservice will bring it back up. This allows us to avoid running
|
||||
// the syncservice needlessly when there is no configured sync server.
|
||||
- (void)spindown;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncServiceInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncServiceXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
|
||||
///
|
||||
+ (NSXPCInterface *)syncServiceInterface;
|
||||
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with syncservice.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
///
|
||||
/// Protocol implemented by santactl sync and used to receive log messages from
|
||||
/// the syncservice during a user initiated sync.
|
||||
///
|
||||
@protocol SNTSyncServiceLogReceiverXPC
|
||||
- (void)didReceiveLog:(NSString *)log;
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
/// Copyright 2020 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.
|
||||
@@ -12,21 +12,32 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
#import "Source/common/SNTXPCSyncServiceInterface.h"
|
||||
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCSyncdInterface
|
||||
@implementation SNTXPCSyncServiceInterface
|
||||
|
||||
+ (NSXPCInterface *)syncdInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
|
||||
+ (NSXPCInterface *)syncServiceInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
forSelector:@selector(postEventsToSyncServer:fromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
return @"com.google.santa.syncservice";
|
||||
}
|
||||
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:YES];
|
||||
c.remoteInterface = [self syncServiceInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,37 +0,0 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by santactl and utilized by santad
|
||||
@protocol SNTSyncdXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncdInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)syncdInterface;
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -15,8 +15,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@@ -28,21 +28,18 @@
|
||||
@protocol SNTUnprivilegedDaemonControlXPC
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
/// Cache Ops
|
||||
///
|
||||
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
|
||||
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
|
||||
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
|
||||
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary,
|
||||
int64_t certificate,
|
||||
int64_t compiler,
|
||||
int64_t transitive))reply;
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
|
||||
int64_t transitive, int64_t teamID))reply;
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
- (void)staticRuleCount:(void (^)(int64_t count))reply;
|
||||
|
||||
///
|
||||
/// Decision ops
|
||||
@@ -59,19 +56,24 @@
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
teamID:(NSString *)teamID
|
||||
reply:(void (^)(SNTEventState))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
///
|
||||
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply;
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply;
|
||||
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
- (void)syncCleanRequired:(void (^)(BOOL))reply;
|
||||
- (void)enableBundles:(void (^)(BOOL))reply;
|
||||
- (void)enableTransitiveWhitelisting:(void (^)(BOOL))reply;
|
||||
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Metrics ops
|
||||
///
|
||||
- (void)metrics:(void (^)(NSDictionary *))reply;
|
||||
|
||||
///
|
||||
/// GUI Ops
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(syncBundleEvent:relatedEvents:)
|
||||
argumentIndex:1
|
||||
ofReply:NO];
|
||||
forSelector:@selector(syncBundleEvent:relatedEvents:)
|
||||
argumentIndex:1
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
|
||||
NSXPCInterface *r =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
|
||||
[self initializeControlInterface:r];
|
||||
|
||||
return r;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
/// Copyright 2016-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.
|
||||
@@ -17,27 +17,18 @@
|
||||
|
||||
#include <libkern/OSAtomic.h>
|
||||
#include <libkern/OSTypes.h>
|
||||
#include <os/log.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <IOKit/IOLib.h>
|
||||
#else // KERNEL
|
||||
// Support for unit testing.
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
// TODO(rah): Consider templatizing these.
|
||||
#define panic(args...) printf(args); printf("\n"); abort()
|
||||
#define IOMallocAligned(sz, alignment) malloc(sz);
|
||||
#define IOFreeAligned(addr, sz) free(addr)
|
||||
#define OSTestAndSet OSAtomicTestAndSet
|
||||
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
|
||||
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
|
||||
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
|
||||
#endif // KERNEL
|
||||
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
/**
|
||||
A type to specialize to help SantaCache with its hashing.
|
||||
@@ -45,12 +36,14 @@
|
||||
The default works for numeric types with a multiplicative hash
|
||||
using a prime near to the golden ratio, per Knuth.
|
||||
*/
|
||||
template<typename T> uint64_t SantaCacheHasher(T const& t) {
|
||||
return t * 11400714819323198549UL;
|
||||
template <typename T>
|
||||
uint64_t SantaCacheHasher(T const &t) {
|
||||
return (uint64_t)t * 11400714819323198549UL;
|
||||
};
|
||||
|
||||
/**
|
||||
A somewhat simple, concurrent linked-list hash table intended for use in IOKit kernel extensions.
|
||||
A somewhat simple, concurrent linked-list hash table intended for use in IOKit
|
||||
kernel extensions.
|
||||
|
||||
The type used for keys must overload the == operator and a specialization of
|
||||
SantaCacheHasher must exist for it.
|
||||
@@ -61,24 +54,28 @@ template<typename T> uint64_t SantaCacheHasher(T const& t) {
|
||||
The number of buckets is calculated as `maximum_size` / `per_bucket`
|
||||
rounded up to the next power of 2. Locking is done per-bucket.
|
||||
*/
|
||||
template<typename KeyT, typename ValueT> class SantaCache {
|
||||
template <typename KeyT, typename ValueT>
|
||||
class SantaCache {
|
||||
public:
|
||||
/**
|
||||
Initialize a newly created cache.
|
||||
|
||||
@param maximum_size The maximum number of entries in this cache. Once this
|
||||
number is reached all the entries will be purged.
|
||||
@param per_bucket The target number of entries in each bucket when cache is full.
|
||||
A higher number will result in better performance but higher memory usage.
|
||||
Cannot be higher than 64 to try and ensure buckets don't overflow.
|
||||
@param per_bucket The target number of entries in each bucket when cache is
|
||||
full. A higher number will result in better performance but higher memory
|
||||
usage. Cannot be higher than 64 to try and ensure buckets don't overflow.
|
||||
*/
|
||||
SantaCache(uint64_t maximum_size = 10000, uint8_t per_bucket = 5) {
|
||||
if (unlikely(per_bucket > maximum_size)) per_bucket = maximum_size;
|
||||
if (unlikely(per_bucket > maximum_size)) per_bucket = (uint8_t)maximum_size;
|
||||
if (unlikely(per_bucket < 1)) per_bucket = 1;
|
||||
if (unlikely(per_bucket > 64)) per_bucket = 64;
|
||||
max_size_ = maximum_size;
|
||||
bucket_count_ = (1 << (32 - __builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
|
||||
buckets_ = (struct bucket *)IOMallocAligned(bucket_count_ * sizeof(struct bucket), 2);
|
||||
bucket_count_ =
|
||||
(1 << (32 -
|
||||
__builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
|
||||
if (unlikely(bucket_count_ > UINT32_MAX)) bucket_count_ = UINT32_MAX;
|
||||
buckets_ = (struct bucket *)malloc(bucket_count_ * sizeof(struct bucket));
|
||||
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
@@ -87,7 +84,7 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
*/
|
||||
~SantaCache() {
|
||||
clear();
|
||||
IOFreeAligned(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
free(buckets_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +117,7 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
@return true if the value was set.
|
||||
*/
|
||||
bool set(const KeyT& key, const ValueT& value) {
|
||||
bool set(const KeyT &key, const ValueT &value) {
|
||||
return set(key, value, {}, false);
|
||||
}
|
||||
|
||||
@@ -138,16 +135,14 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
@return true if the value was set
|
||||
*/
|
||||
bool set(const KeyT& key, const ValueT& value, const ValueT& previous_value) {
|
||||
bool set(const KeyT &key, const ValueT &value, const ValueT &previous_value) {
|
||||
return set(key, value, previous_value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
An alias for `set(key, zero_)`
|
||||
*/
|
||||
inline void remove(const KeyT& key) {
|
||||
set(key, zero_);
|
||||
}
|
||||
inline void remove(const KeyT &key) { set(key, zero_); }
|
||||
|
||||
/**
|
||||
Remove all entries and free bucket memory.
|
||||
@@ -164,7 +159,7 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
struct entry *next_entry = entry->next;
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
free(entry);
|
||||
entry = next_entry;
|
||||
}
|
||||
}
|
||||
@@ -181,24 +176,31 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
/**
|
||||
Return number of entries currently in cache.
|
||||
*/
|
||||
inline uint64_t count() const {
|
||||
return count_;
|
||||
}
|
||||
inline uint64_t count() const { return count_; }
|
||||
|
||||
/**
|
||||
Fill in the per_bucket_counts array with the number of entries in each bucket.
|
||||
Fill in the per_bucket_counts array with the number of entries in each
|
||||
bucket.
|
||||
|
||||
The per_buckets_count array will contain the per-bucket counts, up to the number
|
||||
in array_size. The start_bucket parameter will determine which bucket to start off
|
||||
with and upon return will contain either 0 if no buckets are remaining or the next
|
||||
bucket to begin with when called again.
|
||||
The per_buckets_count array will contain the per-bucket counts, up to the
|
||||
number in array_size. The start_bucket parameter will determine which bucket
|
||||
to start off with and upon return will contain either 0 if no buckets are
|
||||
remaining or the next bucket to begin with when called again.
|
||||
*/
|
||||
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
|
||||
if (per_bucket_counts == nullptr || array_size == nullptr || start_bucket == nullptr) return;
|
||||
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size,
|
||||
uint64_t *start_bucket) {
|
||||
if (per_bucket_counts == nullptr || array_size == nullptr ||
|
||||
start_bucket == nullptr)
|
||||
return;
|
||||
|
||||
uint64_t start = *start_bucket;
|
||||
if (start >= bucket_count_) {
|
||||
*start_bucket = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t size = *array_size;
|
||||
if (start + size > bucket_count_) size = bucket_count_ - start;
|
||||
if (start + size > bucket_count_) size = (uint16_t)(bucket_count_ - start);
|
||||
|
||||
for (uint16_t i = 0; i < size; ++i) {
|
||||
uint16_t count = 0;
|
||||
@@ -245,8 +247,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
@return true if the entry was set, false if it was not
|
||||
*/
|
||||
bool set(const KeyT& key, const ValueT& value,
|
||||
const ValueT& previous_value, bool has_prev_value) {
|
||||
bool set(const KeyT &key, const ValueT &value, const ValueT &previous_value,
|
||||
bool has_prev_value) {
|
||||
struct bucket *bucket = &buckets_[hash(key)];
|
||||
lock(bucket);
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
@@ -268,8 +270,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
} else {
|
||||
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
|
||||
}
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
OSDecrementAtomic(&count_);
|
||||
free(entry);
|
||||
OSAtomicDecrement64((volatile int64_t *)&count_);
|
||||
}
|
||||
|
||||
unlock(bucket);
|
||||
@@ -302,13 +304,13 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
// Allocate a new entry, set the key and value, then put this new entry at
|
||||
// the head of this bucket's linked list.
|
||||
struct entry *new_entry = (struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
struct entry *new_entry = (struct entry *)malloc(sizeof(struct entry));
|
||||
bzero(new_entry, sizeof(struct entry));
|
||||
new_entry->key = key;
|
||||
new_entry->value = value;
|
||||
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
|
||||
OSIncrementAtomic(&count_);
|
||||
OSAtomicIncrement64((volatile int64_t *)&count_);
|
||||
|
||||
unlock(bucket);
|
||||
return true;
|
||||
@@ -318,15 +320,19 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
Lock a bucket. Spins until the lock is acquired.
|
||||
*/
|
||||
inline void lock(struct bucket *bucket) const {
|
||||
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head));
|
||||
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
Unlock a bucket. Panics if the lock wasn't locked.
|
||||
*/
|
||||
inline void unlock(struct bucket *bucket) const {
|
||||
if (unlikely(OSTestAndClear(7, (volatile uint8_t *)&bucket->head))) {
|
||||
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
|
||||
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
|
||||
0)) {
|
||||
os_log_error(OS_LOG_DEFAULT,
|
||||
"SantaCache::unlock(): Tried to unlock an unlocked lock");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,4 +363,6 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/santa_driver/SantaCache.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
|
||||
@interface SantaCacheTest : XCTestCase
|
||||
@end
|
||||
@@ -102,9 +102,8 @@
|
||||
|
||||
// Calculate stdev
|
||||
double accum = 0.0;
|
||||
std::for_each(per_bucket.begin(), per_bucket.end(), [&](const double d) {
|
||||
accum += (d - mean) * (d - mean);
|
||||
});
|
||||
std::for_each(per_bucket.begin(), per_bucket.end(),
|
||||
[&](const double d) { accum += (d - mean) * (d - mean); });
|
||||
double stddev = sqrt(accum / (per_bucket.size() - 1));
|
||||
double maxStdDev = (double)br / 2;
|
||||
XCTAssertLessThanOrEqual(stddev, maxStdDev,
|
||||
@@ -143,13 +142,15 @@
|
||||
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
for (int i = 0; i < 5000; ++i) sut->set(i, 10000-i);
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
sut->set(i, 10000 - i);
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
for (int i = 5000; i < 10000; ++i) sut->set(i, 10000-i);
|
||||
for (int i = 5000; i < 10000; ++i)
|
||||
sut->set(i, 10000 - i);
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
@@ -157,7 +158,8 @@
|
||||
XCTFail("Timed out while setting values for test");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10000; ++i) XCTAssertEqual(sut->get(i), 10000 - i);
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
XCTAssertEqual(sut->get(i), 10000 - i);
|
||||
}
|
||||
|
||||
delete sut;
|
||||
@@ -193,7 +195,8 @@
|
||||
XCTAssertEqual(sut.get(3.1459124), 0);
|
||||
}
|
||||
|
||||
template<> uint64_t SantaCacheHasher<std::string>(std::string const& s) {
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<std::string>(std::string const &s) {
|
||||
return std::hash<std::string>{}(s);
|
||||
}
|
||||
|
||||
@@ -242,16 +245,17 @@ struct S {
|
||||
uint64_t first_val;
|
||||
uint64_t second_val;
|
||||
|
||||
bool operator==(const S& rhs) {
|
||||
bool operator==(const S &rhs) {
|
||||
return first_val == rhs.first_val && second_val == rhs.second_val;
|
||||
}
|
||||
};
|
||||
template<> uint64_t SantaCacheHasher<S>(S const& s) {
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<S>(S const &s) {
|
||||
return SantaCacheHasher<uint64_t>(s.first_val) ^ (SantaCacheHasher<uint64_t>(s.second_val) << 1);
|
||||
}
|
||||
|
||||
- (void)testStructKeys {
|
||||
auto sut = SantaCache<S, uint64_t>(10, 2);
|
||||
auto sut = SantaCache<S, uint64_t>(10, 2);
|
||||
|
||||
S s1 = {1024, 2048};
|
||||
S s2 = {4096, 8192};
|
||||
@@ -265,4 +269,22 @@ template<> uint64_t SantaCacheHasher<S>(S const& s) {
|
||||
XCTAssertEqual(sut.get(s3), 30);
|
||||
}
|
||||
|
||||
- (void)testBucketCounts {
|
||||
auto sut = new SantaCache<uint64_t, uint64_t>(UINT16_MAX, 1);
|
||||
|
||||
// These tests verify that the bucket_counts() function can't be abused
|
||||
// with integer {over,under}flow issues in the input or going out-of-bounds
|
||||
// on the buckets array.
|
||||
uint16_t size = 2048;
|
||||
uint64_t start = (UINT64_MAX - 2047);
|
||||
uint16_t per_bucket_counts[2048];
|
||||
sut->bucket_counts(per_bucket_counts, &size, &start);
|
||||
XCTAssertEqual(start, 0, @"Check a high start can't overflow");
|
||||
|
||||
size = UINT16_MAX;
|
||||
start = UINT16_MAX - 1;
|
||||
sut->bucket_counts(per_bucket_counts, &size, &start);
|
||||
XCTAssertEqual(start, 0, @"Check a large size can't overflow");
|
||||
}
|
||||
|
||||
@end
|
||||
75
Source/common/TestUtils.h
Normal file
75
Source/common/TestUtils.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/// Copyright 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.
|
||||
|
||||
#ifndef SANTA__COMMON__TESTUTILS_H
|
||||
#define SANTA__COMMON__TESTUTILS_H
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#include <bsm/libbsm.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define NOBODY_UID ((unsigned int)-2)
|
||||
#define NOGROUP_GID ((unsigned int)-1)
|
||||
|
||||
// Bubble up googletest expectation failures to XCTest failures
|
||||
#define XCTBubbleMockVerifyAndClearExpectations(mock) \
|
||||
XCTAssertTrue(::testing::Mock::VerifyAndClearExpectations(mock), \
|
||||
"Expected calls were not properly mocked")
|
||||
|
||||
// Pretty print C string match errors
|
||||
#define XCTAssertCStringEqual(got, want) \
|
||||
XCTAssertTrue(strcmp((got), (want)) == 0, @"\nMismatched strings.\n\t got: %s\n\twant: %s", \
|
||||
(got), (want))
|
||||
|
||||
// Pretty print C++ string match errors
|
||||
#define XCTAssertCppStringEqual(got, want) XCTAssertCStringEqual((got).c_str(), (want).c_str())
|
||||
|
||||
#define XCTAssertSemaTrue(s, sec, m) \
|
||||
XCTAssertEqual( \
|
||||
0, dispatch_semaphore_wait((s), dispatch_time(DISPATCH_TIME_NOW, (sec)*NSEC_PER_SEC)), m)
|
||||
|
||||
// Helper to ensure at least `ms` milliseconds are slept, even if the sleep
|
||||
// function returns early due to interrupts.
|
||||
void SleepMS(long ms);
|
||||
|
||||
enum class ActionType {
|
||||
Auth,
|
||||
Notify,
|
||||
};
|
||||
|
||||
//
|
||||
// Helpers to construct various ES structs
|
||||
//
|
||||
|
||||
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver);
|
||||
|
||||
/// Construct a `struct stat` buffer with each member having a unique value.
|
||||
/// @param offset An optional offset to be added to each member. useful when
|
||||
/// a test has multiple stats and you'd like for them each to have different
|
||||
/// values across the members.
|
||||
struct stat MakeStat(int offset = 0);
|
||||
|
||||
es_string_token_t MakeESStringToken(const char *s);
|
||||
es_file_t MakeESFile(const char *path, struct stat sb = {});
|
||||
es_process_t MakeESProcess(es_file_t *file, audit_token_t tok = {}, audit_token_t parent_tok = {});
|
||||
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc,
|
||||
ActionType action_type = ActionType::Notify,
|
||||
uint64_t future_deadline_ms = 100000);
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS();
|
||||
|
||||
#endif
|
||||
145
Source/common/TestUtils.mm
Normal file
145
Source/common/TestUtils.mm
Normal file
@@ -0,0 +1,145 @@
|
||||
/// Copyright 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.
|
||||
|
||||
#include "Source/common/TestUtils.h"
|
||||
|
||||
#include <EndpointSecurity/ESTypes.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <time.h>
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
audit_token_t MakeAuditToken(pid_t pid, pid_t pidver) {
|
||||
return audit_token_t{
|
||||
.val =
|
||||
{
|
||||
0,
|
||||
NOBODY_UID,
|
||||
NOGROUP_GID,
|
||||
NOBODY_UID,
|
||||
NOGROUP_GID,
|
||||
(unsigned int)pid,
|
||||
0,
|
||||
(unsigned int)pidver,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
struct stat MakeStat(int offset) {
|
||||
return (struct stat){
|
||||
.st_dev = 1 + offset,
|
||||
.st_mode = (mode_t)(2 + offset),
|
||||
.st_nlink = (nlink_t)(3 + offset),
|
||||
.st_ino = (uint64_t)(4 + offset),
|
||||
.st_uid = NOBODY_UID,
|
||||
.st_gid = NOGROUP_GID,
|
||||
.st_rdev = 5 + offset,
|
||||
.st_atimespec = {.tv_sec = 100 + offset, .tv_nsec = 200 + offset},
|
||||
.st_mtimespec = {.tv_sec = 101 + offset, .tv_nsec = 21 + offset},
|
||||
.st_ctimespec = {.tv_sec = 102 + offset, .tv_nsec = 202 + offset},
|
||||
.st_birthtimespec = {.tv_sec = 103 + offset, .tv_nsec = 203 + offset},
|
||||
.st_size = 6 + offset,
|
||||
.st_blocks = 7 + offset,
|
||||
.st_blksize = 8 + offset,
|
||||
.st_flags = (uint32_t)(9 + offset),
|
||||
.st_gen = (uint32_t)(10 + offset),
|
||||
};
|
||||
}
|
||||
|
||||
es_string_token_t MakeESStringToken(const char *s) {
|
||||
return es_string_token_t{
|
||||
.length = strlen(s),
|
||||
.data = s,
|
||||
};
|
||||
}
|
||||
|
||||
es_file_t MakeESFile(const char *path, struct stat sb) {
|
||||
return es_file_t{
|
||||
.path = MakeESStringToken(path),
|
||||
.path_truncated = false,
|
||||
.stat = sb,
|
||||
};
|
||||
}
|
||||
|
||||
es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t parent_tok) {
|
||||
return es_process_t{
|
||||
.audit_token = tok,
|
||||
.ppid = audit_token_to_pid(parent_tok),
|
||||
.original_ppid = audit_token_to_pid(parent_tok),
|
||||
.group_id = 111,
|
||||
.session_id = 222,
|
||||
.is_platform_binary = true,
|
||||
.is_es_client = true,
|
||||
.executable = file,
|
||||
.parent_audit_token = parent_tok,
|
||||
};
|
||||
}
|
||||
|
||||
static uint64_t AddMillisToMachTime(uint64_t ms, uint64_t machTime) {
|
||||
static dispatch_once_t onceToken;
|
||||
static mach_timebase_info_data_t timebase;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
mach_timebase_info(&timebase);
|
||||
});
|
||||
|
||||
// Convert given machTime to nanoseconds
|
||||
uint64_t nanoTime = machTime * timebase.numer / timebase.denom;
|
||||
|
||||
// Add the ms offset
|
||||
nanoTime += (ms * NSEC_PER_MSEC);
|
||||
|
||||
// Convert back to machTime
|
||||
return nanoTime * timebase.denom / timebase.numer;
|
||||
}
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
|
||||
// Note: ES message v3 was only in betas.
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return 6;
|
||||
} else if (@available(macOS 12.3, *)) {
|
||||
return 5;
|
||||
} else if (@available(macOS 11.0, *)) {
|
||||
return 4;
|
||||
} else if (@available(macOS 10.15.4, *)) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType action_type,
|
||||
uint64_t future_deadline_ms) {
|
||||
es_message_t es_msg = {
|
||||
.deadline = AddMillisToMachTime(future_deadline_ms, mach_absolute_time()),
|
||||
.process = proc,
|
||||
.action_type =
|
||||
(action_type == ActionType::Notify) ? ES_ACTION_TYPE_NOTIFY : ES_ACTION_TYPE_AUTH,
|
||||
.event_type = et,
|
||||
};
|
||||
|
||||
es_msg.version = MaxSupportedESMessageVersionForCurrentOS();
|
||||
|
||||
return es_msg;
|
||||
}
|
||||
|
||||
void SleepMS(long ms) {
|
||||
struct timespec ts {
|
||||
.tv_sec = ms / 1000, .tv_nsec = (long)((ms % 1000) * NSEC_PER_MSEC),
|
||||
};
|
||||
|
||||
while (nanosleep(&ts, &ts) != 0) {
|
||||
XCTAssertEqual(errno, EINTR);
|
||||
}
|
||||
}
|
||||
501
Source/common/santa.proto
Normal file
501
Source/common/santa.proto
Normal file
@@ -0,0 +1,501 @@
|
||||
//
|
||||
// !!! WARNING !!!
|
||||
// This proto is for demonstration purposes only and will be changing.
|
||||
// Do not rely on this format.
|
||||
//
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option objc_class_prefix = "SNTPB";
|
||||
|
||||
package santa.pb.v1;
|
||||
|
||||
// User ID and associated username
|
||||
message UserInfo {
|
||||
optional int32 uid = 1;
|
||||
optional string name = 2;
|
||||
}
|
||||
|
||||
// Group ID and associated group name
|
||||
message GroupInfo {
|
||||
optional int32 gid = 1;
|
||||
optional string name = 2;
|
||||
}
|
||||
|
||||
// A process is uniquely identified on macOS by its pid and pidversion
|
||||
message ProcessID {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
}
|
||||
|
||||
// Code signature information
|
||||
message CodeSignature {
|
||||
// The code directory hash identifies a specific version of a program
|
||||
optional bytes cdhash = 1;
|
||||
|
||||
// The signing id of the code signature
|
||||
optional string signing_id = 2;
|
||||
|
||||
// The team id of the code signature
|
||||
optional string team_id = 3;
|
||||
}
|
||||
|
||||
// Stat information for a file
|
||||
// Mimics data from `stat(2)`
|
||||
message Stat {
|
||||
optional int32 dev = 1;
|
||||
optional uint32 mode = 2;
|
||||
optional uint32 nlink = 3;
|
||||
optional uint64 ino = 4;
|
||||
optional UserInfo user = 5;
|
||||
optional GroupInfo group = 6;
|
||||
optional int32 rdev = 7;
|
||||
optional google.protobuf.Timestamp access_time = 8;
|
||||
optional google.protobuf.Timestamp modification_time = 9;
|
||||
optional google.protobuf.Timestamp change_time = 10;
|
||||
optional google.protobuf.Timestamp birth_time = 11;
|
||||
optional int64 size = 12;
|
||||
optional int64 blocks = 13;
|
||||
optional int32 blksize = 14;
|
||||
optional uint32 flags = 15;
|
||||
optional int32 gen = 16;
|
||||
}
|
||||
|
||||
// Hash value and metadata describing hash algorithm used
|
||||
message Hash {
|
||||
enum HashAlgo {
|
||||
HASH_ALGO_UNKNOWN = 0;
|
||||
HASH_ALGO_SHA256 = 1;
|
||||
}
|
||||
|
||||
optional HashAlgo type = 1;
|
||||
optional string hash = 2;
|
||||
}
|
||||
|
||||
// File information
|
||||
message FileInfo {
|
||||
// File path
|
||||
optional string path = 1;
|
||||
|
||||
// Whether or not the path is truncated
|
||||
optional bool truncated = 2;
|
||||
|
||||
// Stat information
|
||||
optional Stat stat = 3;
|
||||
|
||||
// Hash of file contents
|
||||
optional Hash hash = 4;
|
||||
}
|
||||
|
||||
// Light variant of `FileInfo` message to help minimize on-disk/on-wire sizes
|
||||
message FileInfoLight {
|
||||
// File path
|
||||
optional string path = 1;
|
||||
|
||||
// Whether or not the path is truncated
|
||||
optional bool truncated = 2;
|
||||
}
|
||||
|
||||
// File descriptor information
|
||||
message FileDescriptor {
|
||||
// Enum types gathered from `<sys/proc_info.h>`
|
||||
enum FDType {
|
||||
FD_TYPE_UNKNOWN = 0;
|
||||
FD_TYPE_ATALK = 1;
|
||||
FD_TYPE_VNODE = 2;
|
||||
FD_TYPE_SOCKET = 3;
|
||||
FD_TYPE_PSHM = 4;
|
||||
FD_TYPE_PSEM = 5;
|
||||
FD_TYPE_KQUEUE = 6;
|
||||
FD_TYPE_PIPE = 7;
|
||||
FD_TYPE_FSEVENTS = 8;
|
||||
FD_TYPE_NETPOLICY = 9;
|
||||
FD_TYPE_CHANNEL = 10;
|
||||
FD_TYPE_NEXUS = 11;
|
||||
}
|
||||
|
||||
// File descriptor value
|
||||
optional int32 fd = 1;
|
||||
|
||||
// Type of file object
|
||||
optional FDType fd_type = 2;
|
||||
|
||||
// Unique id of the pipe for correlation with other file descriptors
|
||||
// pointing to the same or other end of the same pipe
|
||||
// Note: Only valid when `fd_type` is `FD_TYPE_PIPE`
|
||||
optional uint64 pipe_id = 3;
|
||||
}
|
||||
|
||||
// Process information
|
||||
message ProcessInfo {
|
||||
// Process ID of the process
|
||||
optional ProcessID id = 1;
|
||||
|
||||
// Process ID of the parent process
|
||||
optional ProcessID parent_id = 2;
|
||||
|
||||
// Process ID of the process responsible for this one
|
||||
optional ProcessID responsible_id = 3;
|
||||
|
||||
// Original parent ID, remains stable in the event a process is reparented
|
||||
optional int32 original_parent_pid = 4;
|
||||
|
||||
// Process group id the process belongs to
|
||||
optional int32 group_id = 5;
|
||||
|
||||
// Session id the process belongs to
|
||||
optional int32 session_id = 6;
|
||||
|
||||
// Effective user/group info
|
||||
optional UserInfo effective_user = 7;
|
||||
optional GroupInfo effective_group = 8;
|
||||
|
||||
// Real user/group info
|
||||
optional UserInfo real_user = 9;
|
||||
optional GroupInfo real_group = 10;
|
||||
|
||||
// Whether or not the process was signed with Apple certificates
|
||||
optional bool is_platform_binary = 11;
|
||||
|
||||
// Whether or not the process is an ES client
|
||||
optional bool is_es_client = 12;
|
||||
|
||||
// Code signature information for the process
|
||||
optional CodeSignature code_signature = 13;
|
||||
|
||||
// Codesigning flags for the process (from `<Kernel/kern/cs_blobs.h>`)
|
||||
optional uint32 cs_flags = 14;
|
||||
|
||||
// File information for the executable backing this process
|
||||
optional FileInfo executable = 15;
|
||||
|
||||
// File information for the associated TTY
|
||||
optional FileInfoLight tty = 16;
|
||||
|
||||
// Time the process was started
|
||||
optional google.protobuf.Timestamp start_time = 17;
|
||||
}
|
||||
|
||||
// Light variant of ProcessInfo message to help minimize on-disk/on-wire sizes
|
||||
message ProcessInfoLight {
|
||||
// Process ID of the process
|
||||
optional ProcessID id = 1;
|
||||
|
||||
// Process ID of the parent process
|
||||
optional ProcessID parent_id = 2;
|
||||
|
||||
// Original parent ID, remains stable in the event a process is reparented
|
||||
optional int32 original_parent_pid = 3;
|
||||
|
||||
// Process group id the process belongs to
|
||||
optional int32 group_id = 4;
|
||||
|
||||
// Session id the process belongs to
|
||||
optional int32 session_id = 5;
|
||||
|
||||
// Effective user/group info
|
||||
optional UserInfo effective_user = 6;
|
||||
optional GroupInfo effective_group = 7;
|
||||
|
||||
// Real user/group info
|
||||
optional UserInfo real_user = 8;
|
||||
optional GroupInfo real_group = 9;
|
||||
|
||||
// File information for the executable backing this process
|
||||
optional FileInfoLight executable = 10;
|
||||
}
|
||||
|
||||
// Certificate information
|
||||
message CertificateInfo {
|
||||
// Hash of the certificate data
|
||||
optional Hash hash = 1;
|
||||
|
||||
// Common name used in the certificate
|
||||
optional string common_name = 2;
|
||||
}
|
||||
|
||||
// Information about a process execution event
|
||||
message Execution {
|
||||
// The process that executed the new image (e.g. the process that called
|
||||
// `execve(2)` or `posix_spawn(2)``)
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Process info for the newly formed execution
|
||||
optional ProcessInfo target = 2;
|
||||
|
||||
// Script file information
|
||||
// Only valid when a script was executed directly and not as an argument to
|
||||
// an interpreter (e.g. `./foo.sh`, not `/bin/sh ./foo.sh`)
|
||||
optional FileInfo script = 3;
|
||||
|
||||
// The current working directory of the `target` at exec time
|
||||
optional FileInfo working_directory = 4;
|
||||
|
||||
// List of process arguments
|
||||
repeated string args = 5;
|
||||
|
||||
// List of environment variables
|
||||
repeated string envs = 6;
|
||||
|
||||
// List of file descriptors
|
||||
repeated FileDescriptor fds = 7;
|
||||
|
||||
// Whether or not the list of `fds` is complete or contains partial info
|
||||
optional bool fd_list_truncated = 8;
|
||||
|
||||
// Whether or not the target execution was allowed
|
||||
enum Decision {
|
||||
DECISION_UNKNOWN = 0;
|
||||
DECISION_ALLOW = 1;
|
||||
DECISION_DENY = 2;
|
||||
}
|
||||
optional Decision decision = 9;
|
||||
|
||||
// The policy applied when determining the decision
|
||||
enum Reason {
|
||||
REASON_UNKNOWN = 0;
|
||||
REASON_BINARY = 1;
|
||||
REASON_CERT = 2;
|
||||
REASON_COMPILER = 3;
|
||||
REASON_PENDING_TRANSITIVE = 5;
|
||||
REASON_SCOPE = 6;
|
||||
REASON_TEAM_ID = 7;
|
||||
REASON_TRANSITIVE = 8;
|
||||
REASON_LONG_PATH = 9;
|
||||
REASON_NOT_RUNNING = 10;
|
||||
}
|
||||
optional Reason reason = 10;
|
||||
|
||||
// The mode Santa was in when the decision was applied
|
||||
enum Mode {
|
||||
MODE_UNKNOWN = 0;
|
||||
MODE_LOCKDOWN = 1;
|
||||
MODE_MONITOR = 2;
|
||||
}
|
||||
optional Mode mode = 11;
|
||||
|
||||
// Certificate information for the target executable
|
||||
optional CertificateInfo certificate_info = 12;
|
||||
|
||||
// Additional Santa metadata
|
||||
optional string explain = 13;
|
||||
|
||||
// Information known to LaunchServices about the target executable file
|
||||
optional string quarantine_url = 14;
|
||||
|
||||
// The original path on disk of the target executable
|
||||
// Applies when executables are translocated
|
||||
optional string original_path = 15;
|
||||
}
|
||||
|
||||
// Information about a fork event
|
||||
message Fork {
|
||||
// The forking process
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The newly formed child process
|
||||
optional ProcessInfoLight child = 2;
|
||||
}
|
||||
|
||||
// Information about an exit event
|
||||
message Exit {
|
||||
// The process that is exiting
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Exit status code information
|
||||
message Exited {
|
||||
optional int32 exit_status = 1;
|
||||
}
|
||||
|
||||
// Signal code
|
||||
message Signaled {
|
||||
optional int32 signal = 1;
|
||||
}
|
||||
|
||||
// Information on how/why the process exited
|
||||
oneof ExitType {
|
||||
Exited exited = 2;
|
||||
Signaled signaled = 3;
|
||||
Signaled stopped = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Information about an open event
|
||||
message Open {
|
||||
// The process that is opening the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being opened
|
||||
optional FileInfo target = 2;
|
||||
|
||||
// Bitmask of flags used to open the file
|
||||
// Note: Represents the mask applied by the kernel, not the typical `open(2)`
|
||||
// flags (e.g. FREAD, FWRITE instead of O_RDONLY, O_RDWR, etc...)
|
||||
optional int32 flags = 3;
|
||||
}
|
||||
|
||||
// Information about a close event
|
||||
message Close {
|
||||
// The process closing the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being closed
|
||||
optional FileInfo target = 2;
|
||||
|
||||
// Whether or not the file was written to
|
||||
optional bool modified = 3;
|
||||
}
|
||||
|
||||
// Information about an exchagedata event
|
||||
// This event is not applicable to all filesystems (notably APFS)
|
||||
message Exchangedata {
|
||||
// The process that is exchanging the data
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// File information for the two files in the exchangedata operation
|
||||
optional FileInfo file1 = 2;
|
||||
optional FileInfo file2 = 3;
|
||||
}
|
||||
|
||||
// Information about a rename event
|
||||
message Rename {
|
||||
// The process renaming the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The source file being renamed
|
||||
optional FileInfo source = 2;
|
||||
|
||||
// The target path when the rename is complete
|
||||
optional string target = 3;
|
||||
|
||||
// Whether or not the target path previously existed
|
||||
optional bool target_existed = 4;
|
||||
}
|
||||
|
||||
// Information about an unlink event
|
||||
message Unlink {
|
||||
// The process deleting the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being deleted
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
// Information about a link event
|
||||
message Link {
|
||||
// The process performing the link
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The source file being linked
|
||||
optional FileInfo source = 2;
|
||||
|
||||
// The path of the new link
|
||||
optional string target = 3;
|
||||
}
|
||||
|
||||
// Information about when disks are added or removed
|
||||
message Disk {
|
||||
// Whether the disk just appeared or disappeared from the system
|
||||
enum Action {
|
||||
ACTION_UNKNOWN = 0;
|
||||
ACTION_APPEARED = 1;
|
||||
ACTION_DISAPPEARED = 2;
|
||||
}
|
||||
optional Action action = 1;
|
||||
|
||||
// Volume path
|
||||
optional string mount = 2;
|
||||
|
||||
// Volume name
|
||||
optional string volume = 3;
|
||||
|
||||
// Media BSD name
|
||||
optional string bsd_name = 4;
|
||||
|
||||
// Kind of volume
|
||||
optional string fs = 5;
|
||||
|
||||
// Device vendor and model information
|
||||
optional string model = 6;
|
||||
|
||||
// Serial number of the device
|
||||
optional string serial = 7;
|
||||
|
||||
// Device protocol
|
||||
optional string bus = 8;
|
||||
|
||||
// Path of the DMG
|
||||
optional string dmg_path = 9;
|
||||
|
||||
// Time device appeared/disappeared
|
||||
optional google.protobuf.Timestamp appearance = 10;
|
||||
}
|
||||
|
||||
// Information emitted when Santa captures bundle information
|
||||
message Bundle {
|
||||
// This is the hash of the file within the bundle that triggered the event
|
||||
optional Hash file_hash = 1;
|
||||
|
||||
// This is the hash of the hashes of all executables in the bundle
|
||||
optional Hash bundle_hash = 2;
|
||||
|
||||
// Name of the bundle
|
||||
optional string bundle_name = 3;
|
||||
|
||||
// Bundle identifier
|
||||
optional string bundle_id = 4;
|
||||
|
||||
// Bundle path
|
||||
optional string bundle_path = 5;
|
||||
|
||||
// Path of the file within the bundle that triggered the event
|
||||
optional string path = 6;
|
||||
}
|
||||
|
||||
// Information for a transitive allowlist rule
|
||||
message Allowlist {
|
||||
// The process that caused the allowlist rule to be generated
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file the new allowlist rule applies to
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
// A message encapsulating a single event
|
||||
message SantaMessage {
|
||||
// Machine ID of the host emitting this log
|
||||
// Only valid when EnableMachineIDDecoration configuration option is set
|
||||
optional string machine_id = 1;
|
||||
|
||||
// Timestamp when the event occurred
|
||||
optional google.protobuf.Timestamp event_time = 2;
|
||||
|
||||
// Timestamp when Santa finished processing the event
|
||||
optional google.protobuf.Timestamp processed_time = 3;
|
||||
|
||||
// Event type being described by this message
|
||||
oneof event {
|
||||
Execution execution = 10;
|
||||
Fork fork = 11;
|
||||
Exit exit = 12;
|
||||
Close close = 13;
|
||||
Rename rename = 14;
|
||||
Unlink unlink = 15;
|
||||
Link link = 16;
|
||||
Exchangedata exchangedata = 17;
|
||||
Disk disk = 18;
|
||||
Bundle bundle = 19;
|
||||
Allowlist allowlist = 20;
|
||||
};
|
||||
}
|
||||
|
||||
message SantaMessageBatch {
|
||||
repeated SantaMessage messages = 1;
|
||||
}
|
||||
|
||||
message LogBatch {
|
||||
repeated google.protobuf.Any records = 1;
|
||||
}
|
||||
20
Source/common/santa_proto_include_wrapper.h
Normal file
20
Source/common/santa_proto_include_wrapper.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__COMMON_SANTA_PROTO_INCLUDE_WRAPPER_H
|
||||
#define SANTA__COMMON_SANTA_PROTO_INCLUDE_WRAPPER_H
|
||||
|
||||
#include "Source/common/santa.pb.h"
|
||||
|
||||
#endif
|
||||
BIN
Source/common/testdata/32bitplist
vendored
Executable file
BIN
Source/common/testdata/32bitplist
vendored
Executable file
Binary file not shown.
119
Source/gui/BUILD
Normal file
119
Source/gui/BUILD
Normal file
@@ -0,0 +1,119 @@
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-256.png",
|
||||
])
|
||||
|
||||
objc_library(
|
||||
name = "SantaGUI_lib",
|
||||
srcs = [
|
||||
"SNTAboutWindowController.h",
|
||||
"SNTAboutWindowController.m",
|
||||
"SNTAccessibleTextField.h",
|
||||
"SNTAccessibleTextField.m",
|
||||
"SNTAppDelegate.h",
|
||||
"SNTAppDelegate.m",
|
||||
"SNTBinaryMessageWindowController.h",
|
||||
"SNTBinaryMessageWindowController.m",
|
||||
"SNTDeviceMessageWindowController.h",
|
||||
"SNTDeviceMessageWindowController.m",
|
||||
"SNTMessageWindow.h",
|
||||
"SNTMessageWindow.m",
|
||||
"SNTMessageWindowController.h",
|
||||
"SNTMessageWindowController.m",
|
||||
"SNTNotificationManager.h",
|
||||
"SNTNotificationManager.m",
|
||||
"main.m",
|
||||
],
|
||||
hdrs = [
|
||||
"SNTNotificationManager.h",
|
||||
],
|
||||
data = [
|
||||
"Resources/AboutWindow.xib",
|
||||
"Resources/DeviceMessageWindow.xib",
|
||||
"Resources/MessageWindow.xib",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"IOKit",
|
||||
"SecurityInterface",
|
||||
"SystemExtensions",
|
||||
"UserNotifications",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTBlockMessage_SantaGUI",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTStrengthify",
|
||||
"//Source/common:SNTSyncConstants",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
macos_application(
|
||||
name = "Santa",
|
||||
additional_contents = {
|
||||
"//Source/santactl": "MacOS",
|
||||
"//Source/santabundleservice": "MacOS",
|
||||
"//Source/santametricservice": "MacOS",
|
||||
"//Source/santasyncservice": "MacOS",
|
||||
"//Source/santad:com.google.santa.daemon": "Library/SystemExtensions",
|
||||
},
|
||||
app_icons = glob(["Resources/Images.xcassets/**"]),
|
||||
bundle_id = "com.google.santa",
|
||||
bundle_name = "Santa",
|
||||
codesignopts = [
|
||||
"--timestamp",
|
||||
"--force",
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
entitlements = select({
|
||||
"//:adhoc_build": "Santa.app-adhoc.entitlements",
|
||||
# Non-adhoc builds get thier entitlements from the provisioning profile.
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.15",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":SantaGUI_lib"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTNotificationManagerTest",
|
||||
srcs = [
|
||||
"SNTNotificationManagerTest.m",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Cocoa",
|
||||
],
|
||||
deps = [
|
||||
":SantaGUI_lib",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":SNTNotificationManagerTest",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
@@ -1,12 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<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="14460.31"/>
|
||||
<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>
|
||||
@@ -16,7 +17,7 @@
|
||||
<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="3840" height="1577"/>
|
||||
<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"/>
|
||||
@@ -38,7 +39,7 @@
|
||||
<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 whitelisting system for macOS.
|
||||
<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"/>
|
||||
@@ -46,7 +47,7 @@ There are no user-configurable settings.</string>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SRu-Kf-vu5">
|
||||
<rect key="frame" x="130" y="21" width="111" height="32"/>
|
||||
<rect key="frame" x="129" y="21" width="113" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="99" id="JHv-2J-QSe"/>
|
||||
</constraints>
|
||||
@@ -59,7 +60,7 @@ There are no user-configurable settings.</string>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Udo-BY-n7e">
|
||||
<rect key="frame" x="240" y="21" width="111" height="32"/>
|
||||
<rect key="frame" x="239" y="21" width="113" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="99" id="2Xc-ax-2bV"/>
|
||||
</constraints>
|
||||
193
Source/gui/Resources/DeviceMessageWindow.xib
Normal file
193
Source/gui/Resources/DeviceMessageWindow.xib
Normal file
@@ -0,0 +1,193 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user