Compare commits
100 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 |
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
|
||||
14
.bazelrc
@@ -3,3 +3,17 @@ build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
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 +1 @@
|
||||
5.0.0
|
||||
5.3.0
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
name: Check Markdown links
|
||||
name: Check Markdown
|
||||
|
||||
on:
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**.md"
|
||||
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
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'"
|
||||
62
.github/workflows/ci.yml
vendored
@@ -1,49 +1,20 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- 'Source/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'Source/**'
|
||||
|
||||
jobs:
|
||||
preqs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Check If We Need to Run Build/Test
|
||||
id: step1
|
||||
run: |
|
||||
git remote add mainline https://github.com/google/santa.git
|
||||
git fetch mainline main
|
||||
git diff --name-only mainline/main HEAD > files.txt
|
||||
echo "FILES CHANGED: $(wc -l ./files.txt)\n"
|
||||
|
||||
cat files.txt
|
||||
|
||||
build_and_run_tests=0
|
||||
|
||||
for file in `cat files.txt`; do
|
||||
if [[ $file = Source/* ]]; then
|
||||
build_and_run_test=1;
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $build_and_run_test != 0 ]]; then
|
||||
echo "NEED TO RUN BUILD AND TESTS"
|
||||
echo "::set-output name=run_build_and_tests::true"
|
||||
else
|
||||
echo "::set-output name=run_build_and_tests::false"
|
||||
fi
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run linters
|
||||
@@ -53,32 +24,26 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11]
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
|
||||
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]
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run All Tests
|
||||
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci --test_output=errors
|
||||
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=adhoc --test_output=errors
|
||||
|
||||
test_coverage:
|
||||
runs-on: macos-11
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Generate test coverage
|
||||
@@ -89,12 +54,3 @@ jobs:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ./bazel-out/_coverage/_coverage_report.dat
|
||||
flag-name: Unit
|
||||
|
||||
benchmark:
|
||||
runs-on: macos-11
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run All Tests
|
||||
run: ./Testing/benchmark.sh
|
||||
|
||||
2
.github/workflows/continuous.yml
vendored
@@ -10,4 +10,4 @@ jobs:
|
||||
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=ci
|
||||
run: bazel test --test_strategy=exclusive --test_output=errors --runs_per_test 50 -t- :unit_tests --define=SANTA_BUILD_TYPE=adhoc
|
||||
|
||||
21
BUILD
@@ -27,10 +27,11 @@ config_setting(
|
||||
visibility = [":santa_package_group"],
|
||||
)
|
||||
|
||||
# Used to detect CI builds
|
||||
# Adhoc signed - provisioning profiles are not used.
|
||||
# Used for CI runs and dev builds when SIP is disabled.
|
||||
config_setting(
|
||||
name = "ci_build",
|
||||
values = {"define": "SANTA_BUILD_TYPE=ci"},
|
||||
name = "adhoc_build",
|
||||
values = {"define": "SANTA_BUILD_TYPE=adhoc"},
|
||||
visibility = [":santa_package_group"],
|
||||
)
|
||||
|
||||
@@ -73,14 +74,14 @@ launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
run_command(
|
||||
name = "reload",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/gui:Santa",
|
||||
],
|
||||
cmd = """
|
||||
set -e
|
||||
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/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
|
||||
@@ -95,7 +96,7 @@ echo "Time to stop being naughty"
|
||||
genrule(
|
||||
name = "release",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/gui:Santa",
|
||||
"Conf/install.sh",
|
||||
"Conf/uninstall.sh",
|
||||
"Conf/com.google.santa.bundleservice.plist",
|
||||
@@ -190,16 +191,10 @@ test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
"//Source/common:unit_tests",
|
||||
"//Source/gui:unit_tests",
|
||||
"//Source/santactl:unit_tests",
|
||||
"//Source/santad:unit_tests",
|
||||
"//Source/santametricservice:unit_tests",
|
||||
"//Source/santasyncservice:unit_tests",
|
||||
],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "benchmarks",
|
||||
tests = [
|
||||
"//Source/santad:SNTApplicationBenchmark",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -26,6 +26,9 @@ mkdir -p /usr/local/bin
|
||||
# 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
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
/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
|
||||
|
||||
|
||||
12
README.md
@@ -1,13 +1,13 @@
|
||||
# Santa [](https://github.com/google/santa/actions/workflows/ci.yml) [](https://coveralls.io/github/google/santa?branch=main)
|
||||
# Santa [](https://github.com/google/santa/actions/workflows/ci.yml)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/google/santa/main/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 authorization system for macOS. It consists of a system
|
||||
extension that monitors for executions, a daemon that makes execution decisions
|
||||
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
|
||||
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.
|
||||
@@ -16,7 +16,7 @@ It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
The Santa docs are stored in the
|
||||
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
|
||||
at http://santa.dev.
|
||||
at https://santa.dev.
|
||||
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
instructions for developing Santa itself.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["-layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
@@ -11,24 +10,30 @@ licenses(["notice"])
|
||||
proto_library(
|
||||
name = "santa_proto",
|
||||
srcs = ["santa.proto"],
|
||||
features = ["layering_check"],
|
||||
deps = [
|
||||
"@com_google_protobuf//:any_proto",
|
||||
"@com_google_protobuf//:timestamp_proto",
|
||||
],
|
||||
)
|
||||
|
||||
objc_proto_library(
|
||||
name = "santa_objc_proto",
|
||||
copts = ["-fno-objc-arc"],
|
||||
non_arc_srcs = ["Santa.pbobjc.m"],
|
||||
protos = [":santa_proto"],
|
||||
cc_proto_library(
|
||||
name = "santa_cc_proto",
|
||||
deps = [":santa_proto"],
|
||||
)
|
||||
|
||||
# Note: Simple wrapper for a `cc_proto_library` target which cannot be directly
|
||||
# depended upon by an `objc_library` target.
|
||||
cc_library(
|
||||
name = "santa_cc_proto_library_wrapper",
|
||||
hdrs = ["santa_proto_include_wrapper.h"],
|
||||
deps = [
|
||||
":santa_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
features = ["layering_check"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
@@ -49,15 +54,7 @@ objc_library(
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -71,6 +68,7 @@ objc_library(
|
||||
":SNTDeviceEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -85,14 +83,17 @@ objc_library(
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTAllowlistInfo",
|
||||
srcs = ["SNTAllowlistInfo.m"],
|
||||
hdrs = ["SNTAllowlistInfo.h"],
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
textual_hdrs = ["SNTCommonEnums.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -101,11 +102,29 @@ objc_library(
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":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"],
|
||||
@@ -117,6 +136,7 @@ objc_library(
|
||||
srcs = ["SNTFileInfo.m"],
|
||||
hdrs = ["SNTFileInfo.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
@@ -129,7 +149,6 @@ cc_library(
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -144,7 +163,6 @@ cc_library(
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = ["-std=c++11"],
|
||||
features = ["layering_check"],
|
||||
deps = [":SNTLogging"],
|
||||
)
|
||||
|
||||
@@ -152,7 +170,16 @@ 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(
|
||||
@@ -168,7 +195,12 @@ objc_library(
|
||||
cc_library(
|
||||
name = "SNTStrengthify",
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSyncConstants",
|
||||
srcs = ["SNTSyncConstants.m"],
|
||||
hdrs = ["SNTSyncConstants.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -201,10 +233,17 @@ 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",
|
||||
],
|
||||
)
|
||||
@@ -231,6 +270,7 @@ objc_library(
|
||||
srcs = ["SNTXPCSyncServiceInterface.m"],
|
||||
hdrs = ["SNTXPCSyncServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -278,13 +318,40 @@ santa_unit_test(
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/// 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/SNTAllowlistInfo.h"
|
||||
|
||||
@implementation SNTAllowlistInfo
|
||||
|
||||
- (instancetype)initWithPid:(pid_t)pid
|
||||
pidversion:(int)pidver
|
||||
targetPath:(NSString *)targetPath
|
||||
sha256:(NSString *)hash {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_pid = pid;
|
||||
_pidversion = pidver;
|
||||
_targetPath = targetPath;
|
||||
_sha256 = hash;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -119,9 +119,16 @@
|
||||
if (!formatStr.length) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_identifier%"
|
||||
withString:event.fileSHA256];
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%bundle_or_file_identifier%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
|
||||
@@ -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/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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.
|
||||
@@ -27,41 +27,22 @@
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
typedef enum {
|
||||
ACTION_UNSET = 0,
|
||||
ACTION_UNSET,
|
||||
|
||||
// REQUESTS
|
||||
ACTION_REQUEST_SHUTDOWN = 10,
|
||||
ACTION_REQUEST_BINARY = 11,
|
||||
// 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 = 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,
|
||||
ACTION_NOTIFY_FORK = 37,
|
||||
ACTION_NOTIFY_EXIT = 38,
|
||||
|
||||
// ERROR
|
||||
ACTION_ERROR = 99,
|
||||
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 || \
|
||||
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER)
|
||||
|
||||
// Struct to manage vnode IDs
|
||||
typedef struct santa_vnode_id_t {
|
||||
@@ -75,28 +56,4 @@ typedef struct santa_vnode_id_t {
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
santa_vnode_id_t vnode_id;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
int pidversion;
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
char ttypath[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];
|
||||
|
||||
// This points to a copy of the original ES message.
|
||||
void *es_message;
|
||||
|
||||
// This points to an NSArray of the process arguments.
|
||||
void *args_array;
|
||||
} santa_message_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.
|
||||
@@ -57,6 +57,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
SNTEventStateBlockCertificate = 1 << 18,
|
||||
SNTEventStateBlockScope = 1 << 19,
|
||||
SNTEventStateBlockTeamID = 1 << 20,
|
||||
SNTEventStateBlockLongPath = 1 << 21,
|
||||
|
||||
// Bits 24-31 store allow decision types
|
||||
SNTEventStateAllowUnknown = 1 << 24,
|
||||
@@ -93,6 +94,7 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
// The return status of a sync.
|
||||
@@ -119,5 +121,4 @@ typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
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";
|
||||
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.
|
||||
@@ -46,6 +48,33 @@
|
||||
///
|
||||
@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.
|
||||
///
|
||||
@@ -151,12 +180,13 @@
|
||||
|
||||
///
|
||||
/// 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.
|
||||
/// SNTEventLogTypeProtobuf: (BETA) Sent to a file on disk, using maildir format. Use
|
||||
/// mailDirectory to specify a path. Use mailDirectoryFileSizeThresholdKB,
|
||||
/// mailDirectorySizeThresholdMB and mailDirectoryEventMaxFlushTimeSec to configure
|
||||
/// additional maildir format settings.
|
||||
/// 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.
|
||||
///
|
||||
@@ -173,40 +203,40 @@
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectory will provide the base path used for
|
||||
/// saving logs using the maildir format.
|
||||
/// Defaults to /var/db/santa/mail.
|
||||
/// If eventLogType is set to protobuf, spoolDirectory will provide the base path used for
|
||||
/// saving logs using a maildir-like format.
|
||||
/// Defaults to /var/db/santa/spool.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *mailDirectory;
|
||||
@property(readonly, nonatomic) NSString *spoolDirectory;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the mailDirectory.
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the spoolDirectory.
|
||||
/// Defaults to 250.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectoryFileSizeThresholdKB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the spoolDirectory.
|
||||
/// Defaults to 100.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectoryFileSizeThresholdKB;
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the mailDirectory.
|
||||
/// Defaults to 500.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// of time an event will be stored in memory before being written to disk.
|
||||
/// Defaults to 5.0.
|
||||
/// Defaults to 15.0.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) float mailDirectoryEventMaxFlushTimeSec;
|
||||
@property(readonly, nonatomic) float spoolDirectoryEventMaxFlushTimeSec;
|
||||
|
||||
///
|
||||
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
|
||||
@@ -215,17 +245,18 @@
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
|
||||
///
|
||||
/// Use an internal cache for decisions instead of relying on the caching
|
||||
/// mechanism built-in to the EndpointSecurity framework. This may increase
|
||||
/// performance, particularly when Santa is run alongside other system
|
||||
/// extensions.
|
||||
/// Has no effect if the system extension is not being used. Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSysxCache;
|
||||
|
||||
#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.
|
||||
@@ -348,12 +379,6 @@
|
||||
///
|
||||
@property(nonatomic) NSArray<NSString *> *remountUSBMode;
|
||||
|
||||
///
|
||||
/// When `blockUSBMount` is set, this is the message shown to the user when a device is blocked
|
||||
/// If this message is not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *usbBlockMessage;
|
||||
|
||||
///
|
||||
/// If set, this over-rides the default machine ID used for syncing.
|
||||
///
|
||||
@@ -411,6 +436,22 @@
|
||||
///
|
||||
@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.
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2021 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,6 +16,7 @@
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@@ -28,11 +29,15 @@
|
||||
@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
|
||||
@@ -44,8 +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";
|
||||
@@ -60,7 +67,8 @@ static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
|
||||
static NSString *const kAboutText = @"AboutText";
|
||||
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";
|
||||
@@ -80,15 +88,13 @@ static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters
|
||||
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
static NSString *const kEventLogPath = @"EventLogPath";
|
||||
static NSString *const kMailDirectory = @"MailDirectory";
|
||||
static NSString *const kMailDirectoryFileSizeThresholdKB = @"MailDirectoryFileSizeThresholdKB";
|
||||
static NSString *const kMailDirectorySizeThresholdMB = @"MailDirectorySizeThresholdMB";
|
||||
static NSString *const kMailDirectoryEventMaxFlushTimeSec = @"MailDirectoryEventMaxFlushTimeSec";
|
||||
static NSString *const kSpoolDirectory = @"SpoolDirectory";
|
||||
static NSString *const kSpoolDirectoryFileSizeThresholdKB = @"SpoolDirectoryFileSizeThresholdKB";
|
||||
static NSString *const kSpoolDirectorySizeThresholdMB = @"SpoolDirectorySizeThresholdMB";
|
||||
static NSString *const kSpoolDirectoryEventMaxFlushTimeSec = @"SpoolDirectoryEventMaxFlushTimeSec";
|
||||
|
||||
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
|
||||
|
||||
static NSString *const kEnableSysxCache = @"EnableSysxCache";
|
||||
|
||||
static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
|
||||
static NSString *const kIgnoreOtherEndpointSecurityClients = @"IgnoreOtherEndpointSecurityClients";
|
||||
static NSString *const kEnableDebugLogging = @"EnableDebugLogging";
|
||||
@@ -111,6 +117,8 @@ 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";
|
||||
@@ -146,7 +154,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBModeKey : array,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
kSyncCleanRequired : number,
|
||||
kEnableAllEventUploadKey : number,
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
@@ -163,7 +172,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBModeKey : array,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kAboutText : string,
|
||||
kEnableSilentModeKey : number,
|
||||
kAboutTextKey : string,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
@@ -173,6 +183,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
@@ -189,12 +200,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
kMailDirectory : string,
|
||||
kMailDirectoryFileSizeThresholdKB : number,
|
||||
kMailDirectorySizeThresholdMB : number,
|
||||
kMailDirectoryEventMaxFlushTimeSec : number,
|
||||
kSpoolDirectory : string,
|
||||
kSpoolDirectoryFileSizeThresholdKB : number,
|
||||
kSpoolDirectorySizeThresholdMB : number,
|
||||
kSpoolDirectoryEventMaxFlushTimeSec : number,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSysxCache : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
kEnableDebugLogging : number,
|
||||
@@ -207,10 +217,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
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];
|
||||
@@ -278,6 +291,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -286,6 +303,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSilentMode {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingAboutText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -370,19 +391,19 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectory {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectory {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryFileSizeThresholdKB {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectoryFileSizeThresholdKB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectorySizeThresholdMB {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectorySizeThresholdMB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryEventMaxFlushTimeSec {
|
||||
+ (NSSet *)keyPathsForValuesAffectingSpoolDirectoryEventMaxFlushTimeSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
@@ -394,8 +415,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
|
||||
return [self configStateSet];
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingDisableUnknownEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableForkAndExitLogging {
|
||||
@@ -434,6 +459,26 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
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];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
@@ -532,7 +577,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)remountUSBMode {
|
||||
NSArray<NSString *> *args = self.configState[kRemountUSBModeKey];
|
||||
NSArray<NSString *> *args = self.syncState[kRemountUSBModeKey];
|
||||
if (!args) {
|
||||
args = (NSArray<NSString *> *)self.configState[kRemountUSBModeKey];
|
||||
}
|
||||
for (id arg in args) {
|
||||
if (![arg isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
@@ -541,6 +589,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return args;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, SNTRule *> *)staticRules {
|
||||
return self.cachedStaticRules;
|
||||
}
|
||||
|
||||
- (NSURL *)syncBaseURL {
|
||||
NSString *urlString = self.configState[kSyncBaseURLKey];
|
||||
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
|
||||
@@ -562,8 +614,13 @@ 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[kAboutText];
|
||||
return self.configState[kAboutTextKey];
|
||||
}
|
||||
|
||||
- (NSURL *)moreInfoURL {
|
||||
@@ -693,6 +750,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
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;
|
||||
}
|
||||
@@ -702,26 +763,26 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
- (NSString *)mailDirectory {
|
||||
return self.configState[kMailDirectory] ?: @"/var/db/santa/mail";
|
||||
- (NSString *)spoolDirectory {
|
||||
return self.configState[kSpoolDirectory] ?: @"/var/db/santa/spool";
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectoryFileSizeThresholdKB {
|
||||
return self.configState[kMailDirectoryFileSizeThresholdKB]
|
||||
? [self.configState[kMailDirectoryFileSizeThresholdKB] unsignedIntegerValue]
|
||||
- (NSUInteger)spoolDirectoryFileSizeThresholdKB {
|
||||
return self.configState[kSpoolDirectoryFileSizeThresholdKB]
|
||||
? [self.configState[kSpoolDirectoryFileSizeThresholdKB] unsignedIntegerValue]
|
||||
: 250;
|
||||
}
|
||||
|
||||
- (NSUInteger)spoolDirectorySizeThresholdMB {
|
||||
return self.configState[kSpoolDirectorySizeThresholdMB]
|
||||
? [self.configState[kSpoolDirectorySizeThresholdMB] unsignedIntegerValue]
|
||||
: 100;
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectorySizeThresholdMB {
|
||||
return self.configState[kMailDirectorySizeThresholdMB]
|
||||
? [self.configState[kMailDirectorySizeThresholdMB] unsignedIntegerValue]
|
||||
: 500;
|
||||
}
|
||||
|
||||
- (float)mailDirMaxFlushTime {
|
||||
return self.configState[kMailDirectoryEventMaxFlushTimeSec]
|
||||
? [self.configState[kMailDirectoryEventMaxFlushTimeSec] floatValue]
|
||||
: 5.0;
|
||||
- (float)spoolDirectoryEventMaxFlushTimeSec {
|
||||
return self.configState[kSpoolDirectoryEventMaxFlushTimeSec]
|
||||
? [self.configState[kSpoolDirectoryEventMaxFlushTimeSec] floatValue]
|
||||
: 15.0;
|
||||
}
|
||||
|
||||
- (BOOL)enableMachineIDDecoration {
|
||||
@@ -729,9 +790,31 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableSysxCache {
|
||||
NSNumber *number = self.configState[kEnableSysxCache];
|
||||
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 {
|
||||
@@ -775,8 +858,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (BOOL)blockUSBMount {
|
||||
NSNumber *number = self.configState[kBlockUSBMountKey];
|
||||
return number ? [number boolValue] : NO;
|
||||
NSNumber *n = self.syncState[kBlockUSBMountKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kBlockUSBMountKey] boolValue];
|
||||
}
|
||||
|
||||
///
|
||||
@@ -879,7 +964,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
syncState[kAllowedPathRegexKey] = [syncState[kAllowedPathRegexKey] pattern];
|
||||
syncState[kBlockedPathRegexKey] = [syncState[kBlockedPathRegexKey] pattern];
|
||||
[syncState writeToFile:kSyncStateFilePath atomically:YES];
|
||||
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0644}
|
||||
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0600}
|
||||
ofItemAtPath:kSyncStateFilePath
|
||||
error:NULL];
|
||||
}
|
||||
@@ -936,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
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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.
|
||||
@@ -25,6 +25,8 @@
|
||||
#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.
|
||||
@interface MachHeaderWithOffset : NSObject
|
||||
@@ -48,6 +50,7 @@
|
||||
@property NSFileHandle *fileHandle;
|
||||
@property NSUInteger fileSize;
|
||||
@property NSString *fileOwnerHomeDir;
|
||||
@property NSString *sha256Storage;
|
||||
|
||||
// Cached properties
|
||||
@property NSBundle *bundleRef;
|
||||
@@ -63,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;
|
||||
@@ -76,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"
|
||||
@@ -88,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);
|
||||
}
|
||||
@@ -214,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
|
||||
|
||||
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
@@ -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
@@ -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
|
||||
@@ -108,6 +108,11 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType);
|
||||
*/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Resets all the metrics in this set. Intended only for testing.
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
* Add a root label to the MetricSet.
|
||||
*/
|
||||
|
||||
@@ -280,15 +280,12 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
if (_fieldNames.count == 0) {
|
||||
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
|
||||
} else {
|
||||
for (NSString *fieldName in _fieldNames) {
|
||||
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
|
||||
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
|
||||
}
|
||||
|
||||
metricDict[@"fields"][fieldName] = fieldVals;
|
||||
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
|
||||
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
|
||||
}
|
||||
metricDict[@"fields"][[_fieldNames componentsJoinedByString:@","]] = fieldVals;
|
||||
}
|
||||
return metricDict;
|
||||
}
|
||||
@@ -485,6 +482,10 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
- (void)addRootLabel:(NSString *)label value:(NSString *)value {
|
||||
@synchronized(self) {
|
||||
_rootLabels[label] = value;
|
||||
|
||||
@@ -672,4 +672,35 @@
|
||||
output);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testEnsureMetricsWithMultipleFieldNamesSerializeOnce {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
|
||||
username:@"testUser"];
|
||||
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"client", @"event_type" ]
|
||||
helpText:@"Count of events on the host for a given ES client"];
|
||||
[c incrementBy:1 forFieldValues:@[ @"device_manager", @"auth_mount" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/santa/events" : @{
|
||||
@"description" : @"Count of events on the host for a given ES client",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"client,event_type" : @[
|
||||
@{
|
||||
@"value" : @"device_manager,auth_mount",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
NSDictionary *got = [metricSet export][@"metrics"];
|
||||
XCTAssertEqualObjects(expected, got, @"metrics do not match expected");
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -1,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.
|
||||
@@ -51,18 +51,23 @@
|
||||
/// Designated initializer.
|
||||
///
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp;
|
||||
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)initWithIdentifier:(NSString *)identifier
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg;
|
||||
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,6 +13,7 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
|
||||
@interface SNTRule ()
|
||||
@property(readwrite) NSUInteger timestamp;
|
||||
@@ -48,6 +49,60 @@
|
||||
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
|
||||
|
||||
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.
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
|
||||
|
||||
ENCODE(self.signingChain, @"signingChain");
|
||||
ENCODE(self.teamID, @"teamID");
|
||||
|
||||
ENCODE(self.executingUser, @"executingUser");
|
||||
ENCODE(self.occurrenceDate, @"occurrenceDate");
|
||||
@@ -93,6 +94,7 @@
|
||||
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
|
||||
|
||||
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
|
||||
_teamID = DECODE(NSString, @"teamID");
|
||||
|
||||
_executingUser = DECODE(NSString, @"executingUser");
|
||||
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
|
||||
|
||||
@@ -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,10 +12,14 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
// clang-format off
|
||||
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define WEAKIFY(var) __weak __typeof(var) Weak_##var = (var);
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -50,6 +50,8 @@ 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;
|
||||
@@ -91,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;
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTSyncConstants.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
|
||||
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
|
||||
|
||||
@@ -51,6 +51,8 @@ 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";
|
||||
@@ -92,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";
|
||||
@@ -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.
|
||||
@@ -18,8 +18,11 @@
|
||||
@implementation SNTSystemInfo
|
||||
|
||||
+ (NSString *)serialNumber {
|
||||
#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(
|
||||
@@ -31,8 +34,11 @@
|
||||
}
|
||||
|
||||
+ (NSString *)hardwareUUID {
|
||||
#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(
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
@protocol SNTDaemonControlXPC <SNTUnprivilegedDaemonControlXPC>
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
/// Cache ops
|
||||
///
|
||||
- (void)flushCache:(void (^)(BOOL))reply;
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
/// 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;
|
||||
@@ -51,6 +50,8 @@
|
||||
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply;
|
||||
- (void)setEnableBundles:(BOOL)bundlesEnabled 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
|
||||
|
||||
@@ -27,10 +27,16 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
@implementation SNTXPCControlInterface
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
#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 {
|
||||
|
||||
@@ -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/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@@ -28,12 +28,10 @@
|
||||
@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
|
||||
@@ -41,6 +39,7 @@
|
||||
- (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
|
||||
@@ -64,7 +63,6 @@
|
||||
/// 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;
|
||||
|
||||
@@ -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,6 +17,7 @@
|
||||
|
||||
#include <libkern/OSAtomic.h>
|
||||
#include <libkern/OSTypes.h>
|
||||
#include <os/log.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
@@ -26,11 +27,6 @@
|
||||
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
#define panic(args...) \
|
||||
printf(args); \
|
||||
printf("\n"); \
|
||||
abort()
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@@ -334,7 +330,9 @@ class SantaCache {
|
||||
inline void unlock(struct bucket *bucket) const {
|
||||
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
|
||||
0)) {
|
||||
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
|
||||
os_log_error(OS_LOG_DEFAULT,
|
||||
"SantaCache::unlock(): Tried to unlock an unlocked lock");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//
|
||||
// !!! WARNING !!!
|
||||
// This proto is in beta format and subject to change.
|
||||
// This proto is for demonstration purposes only and will be changing.
|
||||
// Do not rely on this format.
|
||||
//
|
||||
|
||||
syntax = "proto3";
|
||||
@@ -10,134 +11,489 @@ import "google/protobuf/timestamp.proto";
|
||||
|
||||
option objc_class_prefix = "SNTPB";
|
||||
|
||||
package santa;
|
||||
package santa.pb.v1;
|
||||
|
||||
message ProcessInfo {
|
||||
// User ID and associated username
|
||||
message UserInfo {
|
||||
optional int32 uid = 1;
|
||||
optional string name = 2;
|
||||
}
|
||||
|
||||
// Group ID and associated group name
|
||||
message GroupInfo {
|
||||
optional int32 gid = 1;
|
||||
optional string name = 2;
|
||||
}
|
||||
|
||||
// A process is uniquely identified on macOS by its pid and pidversion
|
||||
message ProcessID {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional int32 ppid = 3;
|
||||
optional int32 uid = 4;
|
||||
optional string user = 5;
|
||||
optional int32 gid = 6;
|
||||
optional string group = 7;
|
||||
}
|
||||
|
||||
message FileModification {
|
||||
enum Action {
|
||||
ACTION_UNKNOWN = 0;
|
||||
ACTION_DELETE = 1;
|
||||
ACTION_EXCHANGE = 2;
|
||||
ACTION_LINK = 3;
|
||||
ACTION_RENAME = 4;
|
||||
ACTION_WRITE = 5;
|
||||
// Code signature information
|
||||
message CodeSignature {
|
||||
// The code directory hash identifies a specific version of a program
|
||||
optional bytes cdhash = 1;
|
||||
|
||||
// The signing id of the code signature
|
||||
optional string signing_id = 2;
|
||||
|
||||
// The team id of the code signature
|
||||
optional string team_id = 3;
|
||||
}
|
||||
|
||||
// Stat information for a file
|
||||
// Mimics data from `stat(2)`
|
||||
message Stat {
|
||||
optional int32 dev = 1;
|
||||
optional uint32 mode = 2;
|
||||
optional uint32 nlink = 3;
|
||||
optional uint64 ino = 4;
|
||||
optional UserInfo user = 5;
|
||||
optional GroupInfo group = 6;
|
||||
optional int32 rdev = 7;
|
||||
optional google.protobuf.Timestamp access_time = 8;
|
||||
optional google.protobuf.Timestamp modification_time = 9;
|
||||
optional google.protobuf.Timestamp change_time = 10;
|
||||
optional google.protobuf.Timestamp birth_time = 11;
|
||||
optional int64 size = 12;
|
||||
optional int64 blocks = 13;
|
||||
optional int32 blksize = 14;
|
||||
optional uint32 flags = 15;
|
||||
optional int32 gen = 16;
|
||||
}
|
||||
|
||||
// Hash value and metadata describing hash algorithm used
|
||||
message Hash {
|
||||
enum HashAlgo {
|
||||
HASH_ALGO_UNKNOWN = 0;
|
||||
HASH_ALGO_SHA256 = 1;
|
||||
}
|
||||
|
||||
optional Action action = 1;
|
||||
optional string path = 2;
|
||||
optional string newpath = 3;
|
||||
optional string process = 4;
|
||||
optional string process_path = 5;
|
||||
optional ProcessInfo process_info = 6;
|
||||
optional string machine_id = 7;
|
||||
optional HashAlgo type = 1;
|
||||
optional string hash = 2;
|
||||
}
|
||||
|
||||
// File information
|
||||
message FileInfo {
|
||||
// File path
|
||||
optional string path = 1;
|
||||
|
||||
// Whether or not the path is truncated
|
||||
optional bool truncated = 2;
|
||||
|
||||
// Stat information
|
||||
optional Stat stat = 3;
|
||||
|
||||
// Hash of file contents
|
||||
optional Hash hash = 4;
|
||||
}
|
||||
|
||||
// Light variant of `FileInfo` message to help minimize on-disk/on-wire sizes
|
||||
message FileInfoLight {
|
||||
// File path
|
||||
optional string path = 1;
|
||||
|
||||
// Whether or not the path is truncated
|
||||
optional bool truncated = 2;
|
||||
}
|
||||
|
||||
// File descriptor information
|
||||
message FileDescriptor {
|
||||
// Enum types gathered from `<sys/proc_info.h>`
|
||||
enum FDType {
|
||||
FD_TYPE_UNKNOWN = 0;
|
||||
FD_TYPE_ATALK = 1;
|
||||
FD_TYPE_VNODE = 2;
|
||||
FD_TYPE_SOCKET = 3;
|
||||
FD_TYPE_PSHM = 4;
|
||||
FD_TYPE_PSEM = 5;
|
||||
FD_TYPE_KQUEUE = 6;
|
||||
FD_TYPE_PIPE = 7;
|
||||
FD_TYPE_FSEVENTS = 8;
|
||||
FD_TYPE_NETPOLICY = 9;
|
||||
FD_TYPE_CHANNEL = 10;
|
||||
FD_TYPE_NEXUS = 11;
|
||||
}
|
||||
|
||||
// File descriptor value
|
||||
optional int32 fd = 1;
|
||||
|
||||
// Type of file object
|
||||
optional FDType fd_type = 2;
|
||||
|
||||
// Unique id of the pipe for correlation with other file descriptors
|
||||
// pointing to the same or other end of the same pipe
|
||||
// Note: Only valid when `fd_type` is `FD_TYPE_PIPE`
|
||||
optional uint64 pipe_id = 3;
|
||||
}
|
||||
|
||||
// Process information
|
||||
message ProcessInfo {
|
||||
// Process ID of the process
|
||||
optional ProcessID id = 1;
|
||||
|
||||
// Process ID of the parent process
|
||||
optional ProcessID parent_id = 2;
|
||||
|
||||
// Process ID of the process responsible for this one
|
||||
optional ProcessID responsible_id = 3;
|
||||
|
||||
// Original parent ID, remains stable in the event a process is reparented
|
||||
optional int32 original_parent_pid = 4;
|
||||
|
||||
// Process group id the process belongs to
|
||||
optional int32 group_id = 5;
|
||||
|
||||
// Session id the process belongs to
|
||||
optional int32 session_id = 6;
|
||||
|
||||
// Effective user/group info
|
||||
optional UserInfo effective_user = 7;
|
||||
optional GroupInfo effective_group = 8;
|
||||
|
||||
// Real user/group info
|
||||
optional UserInfo real_user = 9;
|
||||
optional GroupInfo real_group = 10;
|
||||
|
||||
// Whether or not the process was signed with Apple certificates
|
||||
optional bool is_platform_binary = 11;
|
||||
|
||||
// Whether or not the process is an ES client
|
||||
optional bool is_es_client = 12;
|
||||
|
||||
// Code signature information for the process
|
||||
optional CodeSignature code_signature = 13;
|
||||
|
||||
// Codesigning flags for the process (from `<Kernel/kern/cs_blobs.h>`)
|
||||
optional uint32 cs_flags = 14;
|
||||
|
||||
// File information for the executable backing this process
|
||||
optional FileInfo executable = 15;
|
||||
|
||||
// File information for the associated TTY
|
||||
optional FileInfoLight tty = 16;
|
||||
|
||||
// Time the process was started
|
||||
optional google.protobuf.Timestamp start_time = 17;
|
||||
}
|
||||
|
||||
// Light variant of ProcessInfo message to help minimize on-disk/on-wire sizes
|
||||
message ProcessInfoLight {
|
||||
// Process ID of the process
|
||||
optional ProcessID id = 1;
|
||||
|
||||
// Process ID of the parent process
|
||||
optional ProcessID parent_id = 2;
|
||||
|
||||
// Original parent ID, remains stable in the event a process is reparented
|
||||
optional int32 original_parent_pid = 3;
|
||||
|
||||
// Process group id the process belongs to
|
||||
optional int32 group_id = 4;
|
||||
|
||||
// Session id the process belongs to
|
||||
optional int32 session_id = 5;
|
||||
|
||||
// Effective user/group info
|
||||
optional UserInfo effective_user = 6;
|
||||
optional GroupInfo effective_group = 7;
|
||||
|
||||
// Real user/group info
|
||||
optional UserInfo real_user = 8;
|
||||
optional GroupInfo real_group = 9;
|
||||
|
||||
// File information for the executable backing this process
|
||||
optional FileInfoLight executable = 10;
|
||||
}
|
||||
|
||||
// Certificate information
|
||||
message CertificateInfo {
|
||||
// Hash of the certificate data
|
||||
optional Hash hash = 1;
|
||||
|
||||
// Common name used in the certificate
|
||||
optional string common_name = 2;
|
||||
}
|
||||
|
||||
// Information about a process execution event
|
||||
message Execution {
|
||||
// The process that executed the new image (e.g. the process that called
|
||||
// `execve(2)` or `posix_spawn(2)``)
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Process info for the newly formed execution
|
||||
optional ProcessInfo target = 2;
|
||||
|
||||
// Script file information
|
||||
// Only valid when a script was executed directly and not as an argument to
|
||||
// an interpreter (e.g. `./foo.sh`, not `/bin/sh ./foo.sh`)
|
||||
optional FileInfo script = 3;
|
||||
|
||||
// The current working directory of the `target` at exec time
|
||||
optional FileInfo working_directory = 4;
|
||||
|
||||
// List of process arguments
|
||||
repeated 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_NOT_RUNNING = 4;
|
||||
REASON_PENDING_TRANSITIVE = 5;
|
||||
REASON_SCOPE = 6;
|
||||
REASON_TEAM_ID = 7;
|
||||
REASON_TRANSITIVE = 8;
|
||||
REASON_LONG_PATH = 9;
|
||||
REASON_NOT_RUNNING = 10;
|
||||
}
|
||||
optional Reason reason = 10;
|
||||
|
||||
// The mode Santa was in when the decision was applied
|
||||
enum Mode {
|
||||
MODE_UNKNOWN = 0;
|
||||
MODE_LOCKDOWN = 1;
|
||||
MODE_MONITOR = 2;
|
||||
}
|
||||
optional Mode mode = 11;
|
||||
|
||||
optional Decision decision = 1;
|
||||
optional Reason reason = 2;
|
||||
optional string explain = 3;
|
||||
optional string sha256 = 4;
|
||||
optional string cert_sha256 = 5;
|
||||
optional string cert_cn = 6;
|
||||
optional string quarantine_url = 7;
|
||||
optional ProcessInfo process_info = 8;
|
||||
optional Mode mode = 9;
|
||||
optional string path = 10;
|
||||
optional string original_path = 11;
|
||||
repeated string args = 12;
|
||||
optional string machine_id = 13;
|
||||
// Certificate information for the target executable
|
||||
optional CertificateInfo certificate_info = 12;
|
||||
|
||||
// Additional Santa metadata
|
||||
optional string explain = 13;
|
||||
|
||||
// Information known to LaunchServices about the target executable file
|
||||
optional string quarantine_url = 14;
|
||||
|
||||
// The original path on disk of the target executable
|
||||
// Applies when executables are translocated
|
||||
optional string original_path = 15;
|
||||
}
|
||||
|
||||
message DiskAppeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
optional string fs = 4;
|
||||
optional string model = 5;
|
||||
optional string serial = 6;
|
||||
optional string bus = 7;
|
||||
optional string dmg_path = 8;
|
||||
optional string appearance = 9;
|
||||
// Information about a fork event
|
||||
message Fork {
|
||||
// The forking process
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The newly formed child process
|
||||
optional ProcessInfoLight child = 2;
|
||||
}
|
||||
|
||||
message DiskDisappeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
// Information about an exit event
|
||||
message Exit {
|
||||
// The process that is exiting
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Exit status code information
|
||||
message Exited {
|
||||
optional int32 exit_status = 1;
|
||||
}
|
||||
|
||||
// Signal code
|
||||
message Signaled {
|
||||
optional int32 signal = 1;
|
||||
}
|
||||
|
||||
// Information on how/why the process exited
|
||||
oneof ExitType {
|
||||
Exited exited = 2;
|
||||
Signaled signaled = 3;
|
||||
Signaled stopped = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Information about an open event
|
||||
message Open {
|
||||
// The process that is opening the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being opened
|
||||
optional FileInfo target = 2;
|
||||
|
||||
// Bitmask of flags used to open the file
|
||||
// Note: Represents the mask applied by the kernel, not the typical `open(2)`
|
||||
// flags (e.g. FREAD, FWRITE instead of O_RDONLY, O_RDWR, etc...)
|
||||
optional int32 flags = 3;
|
||||
}
|
||||
|
||||
// Information about a close event
|
||||
message Close {
|
||||
// The process closing the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being closed
|
||||
optional FileInfo target = 2;
|
||||
|
||||
// Whether or not the file was written to
|
||||
optional bool modified = 3;
|
||||
}
|
||||
|
||||
// Information about an exchagedata event
|
||||
// This event is not applicable to all filesystems (notably APFS)
|
||||
message Exchangedata {
|
||||
// The process that is exchanging the data
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// File information for the two files in the exchangedata operation
|
||||
optional FileInfo file1 = 2;
|
||||
optional FileInfo file2 = 3;
|
||||
}
|
||||
|
||||
// Information about a rename event
|
||||
message Rename {
|
||||
// The process renaming the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The source file being renamed
|
||||
optional FileInfo source = 2;
|
||||
|
||||
// The target path when the rename is complete
|
||||
optional string target = 3;
|
||||
|
||||
// Whether or not the target path previously existed
|
||||
optional bool target_existed = 4;
|
||||
}
|
||||
|
||||
// Information about an unlink event
|
||||
message Unlink {
|
||||
// The process deleting the file
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file being deleted
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
// Information about a link event
|
||||
message Link {
|
||||
// The process performing the link
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The source file being linked
|
||||
optional FileInfo source = 2;
|
||||
|
||||
// The path of the new link
|
||||
optional string target = 3;
|
||||
}
|
||||
|
||||
// Information about when disks are added or removed
|
||||
message Disk {
|
||||
// Whether the disk just appeared or disappeared from the system
|
||||
enum Action {
|
||||
ACTION_UNKNOWN = 0;
|
||||
ACTION_APPEARED = 1;
|
||||
ACTION_DISAPPEARED = 2;
|
||||
}
|
||||
optional Action action = 1;
|
||||
|
||||
// Volume path
|
||||
optional string mount = 2;
|
||||
|
||||
// Volume name
|
||||
optional string volume = 3;
|
||||
|
||||
// Media BSD name
|
||||
optional string bsd_name = 4;
|
||||
|
||||
// Kind of volume
|
||||
optional string fs = 5;
|
||||
|
||||
// Device vendor and model information
|
||||
optional string model = 6;
|
||||
|
||||
// Serial number of the device
|
||||
optional string serial = 7;
|
||||
|
||||
// Device protocol
|
||||
optional string bus = 8;
|
||||
|
||||
// Path of the DMG
|
||||
optional string dmg_path = 9;
|
||||
|
||||
// Time device appeared/disappeared
|
||||
optional google.protobuf.Timestamp appearance = 10;
|
||||
}
|
||||
|
||||
// Information emitted when Santa captures bundle information
|
||||
message Bundle {
|
||||
// This is the hash of the file within the bundle that triggered the event
|
||||
optional string sha256 = 1;
|
||||
optional Hash file_hash = 1;
|
||||
|
||||
// This is the hash of the hashes of all executables in the bundle
|
||||
optional string bundle_hash = 2;
|
||||
optional Hash bundle_hash = 2;
|
||||
|
||||
// Name of the bundle
|
||||
optional string bundle_name = 3;
|
||||
|
||||
// Bundle identifier
|
||||
optional string bundle_id = 4;
|
||||
|
||||
// Bundle path
|
||||
optional string bundle_path = 5;
|
||||
|
||||
// Path of the file within the bundle that triggered the event
|
||||
optional string path = 6;
|
||||
}
|
||||
|
||||
message Fork {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
message Exit {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
// Information for a transitive allowlist rule
|
||||
message Allowlist {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional string path = 3;
|
||||
optional string sha256 = 4;
|
||||
// The process that caused the allowlist rule to be generated
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// The file the new allowlist rule applies to
|
||||
optional FileInfo target = 2;
|
||||
}
|
||||
|
||||
// A message encapsulating a single event
|
||||
message SantaMessage {
|
||||
google.protobuf.Timestamp event_time = 1;
|
||||
// Machine ID of the host emitting this log
|
||||
// Only valid when EnableMachineIDDecoration configuration option is set
|
||||
optional string machine_id = 1;
|
||||
|
||||
oneof message {
|
||||
FileModification file_modification = 2;
|
||||
Execution execution = 3;
|
||||
DiskAppeared disk_appeared = 4;
|
||||
DiskDisappeared disk_disappeared = 5;
|
||||
Bundle bundle = 6;
|
||||
Fork fork = 7;
|
||||
Exit exit = 8;
|
||||
Allowlist allowlist = 9;
|
||||
}
|
||||
// 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 {
|
||||
|
||||
20
Source/common/santa_proto_include_wrapper.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__COMMON_SANTA_PROTO_INCLUDE_WRAPPER_H
|
||||
#define SANTA__COMMON_SANTA_PROTO_INCLUDE_WRAPPER_H
|
||||
|
||||
#include "Source/common/santa.pb.h"
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,12 @@
|
||||
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",
|
||||
])
|
||||
@@ -27,6 +32,9 @@ objc_library(
|
||||
"SNTNotificationManager.m",
|
||||
"main.m",
|
||||
],
|
||||
hdrs = [
|
||||
"SNTNotificationManager.h",
|
||||
],
|
||||
data = [
|
||||
"Resources/AboutWindow.xib",
|
||||
"Resources/DeviceMessageWindow.xib",
|
||||
@@ -36,13 +44,19 @@ objc_library(
|
||||
"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",
|
||||
],
|
||||
@@ -65,14 +79,41 @@ macos_application(
|
||||
"--force",
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
entitlements = "Santa.app.entitlements",
|
||||
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({
|
||||
"//:ci_build": None,
|
||||
"//conditions:default": "Santa_Dev.provisionprofile",
|
||||
"//: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"],
|
||||
)
|
||||
|
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 |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTAboutWindowController.h"
|
||||
#import "Source/gui/SNTAboutWindowController.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTAccessibleTextField.h"
|
||||
#import "Source/gui/SNTAccessibleTextField.h"
|
||||
|
||||
@implementation SNTAccessibleTextField
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTAppDelegate.h"
|
||||
#import "Source/gui/SNTAppDelegate.h"
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santa/SNTAboutWindowController.h"
|
||||
#import "Source/santa/SNTNotificationManager.h"
|
||||
#import "Source/gui/SNTAboutWindowController.h"
|
||||
#import "Source/gui/SNTNotificationManager.h"
|
||||
|
||||
@interface SNTAppDelegate ()
|
||||
@property SNTAboutWindowController *aboutWindowController;
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/gui/SNTBinaryMessageWindowController.h"
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
@@ -20,7 +20,7 @@
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
#import "Source/gui/SNTMessageWindow.h"
|
||||
|
||||
@interface SNTBinaryMessageWindowController ()
|
||||
/// The custom message to display for this event
|
||||
@@ -139,7 +139,9 @@
|
||||
- (NSString *)publisherInfo {
|
||||
MOLCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
if ([leafCert.commonName isEqualToString:@"Apple Mac OS Application Signing"]) {
|
||||
return [NSString stringWithFormat:@"App Store (Team ID: %@)", self.event.teamID];
|
||||
} else if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
@@ -14,7 +14,7 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/gui/SNTDeviceMessageWindowController.h"
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
#import "Source/gui/SNTMessageWindow.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
#import "Source/gui/SNTMessageWindow.h"
|
||||
|
||||
@implementation SNTMessageWindow
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
#import "Source/gui/SNTMessageWindow.h"
|
||||
|
||||
@implementation SNTMessageWindowController
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
return @"";
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,9 +15,9 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/santa/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/santa/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
#import "Source/gui/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/gui/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
///
|
||||
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.
|
||||
@@ -12,9 +12,11 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTNotificationManager.h"
|
||||
#import "Source/gui/SNTNotificationManager.h"
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
@@ -22,8 +24,9 @@
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
|
||||
@@ -58,7 +61,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[self.pendingNotifications removeObject:self.currentWindowController];
|
||||
self.currentWindowController = nil;
|
||||
|
||||
if ([self.pendingNotifications count]) {
|
||||
if (self.pendingNotifications.count) {
|
||||
[self showQueuedWindow];
|
||||
} else {
|
||||
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
|
||||
@@ -83,9 +86,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
|
||||
for (SNTMessageWindowController *msg in self.pendingNotifications) {
|
||||
if ([msg messageHash] == [pendingMsg messageHash]) {
|
||||
return YES;
|
||||
}
|
||||
if ([[msg messageHash] isEqual:[pendingMsg messageHash]]) return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@@ -113,12 +114,59 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
[self postDistributedNotification:pendingMsg];
|
||||
|
||||
if (!self.currentWindowController) {
|
||||
[self showQueuedWindow];
|
||||
}
|
||||
}
|
||||
|
||||
// For blocked execution notifications, post an NSDistributedNotificationCenter
|
||||
// notification with the important details from the stored event. Distributed
|
||||
// notifications are system-wide broadcasts that can be sent by apps and observed
|
||||
// from separate processes. This allows users of Santa to write tools that
|
||||
// perform actions when we block execution, such as trigger management tools or
|
||||
// display an enterprise-specific UI (which is particularly useful when combined
|
||||
// with the EnableSilentMode configuration option, to disable Santa's standard UI).
|
||||
- (void)postDistributedNotification:(SNTMessageWindowController *)pendingMsg {
|
||||
if (![pendingMsg isKindOfClass:[SNTBinaryMessageWindowController class]]) {
|
||||
return;
|
||||
}
|
||||
SNTBinaryMessageWindowController *wc = (SNTBinaryMessageWindowController *)pendingMsg;
|
||||
NSDistributedNotificationCenter *dc = [NSDistributedNotificationCenter defaultCenter];
|
||||
NSMutableArray<NSDictionary *> *signingChain =
|
||||
[NSMutableArray arrayWithCapacity:wc.event.signingChain.count];
|
||||
for (MOLCertificate *cert in wc.event.signingChain) {
|
||||
[signingChain addObject:@{
|
||||
kCertSHA256 : cert.SHA256 ?: @"",
|
||||
kCertCN : cert.commonName ?: @"",
|
||||
kCertOrg : cert.orgName ?: @"",
|
||||
kCertOU : cert.orgUnit ?: @"",
|
||||
kCertValidFrom : @([cert.validFrom timeIntervalSince1970]) ?: @0,
|
||||
kCertValidUntil : @([cert.validUntil timeIntervalSince1970]) ?: @0,
|
||||
}];
|
||||
}
|
||||
NSDictionary *userInfo = @{
|
||||
kFileSHA256 : wc.event.fileSHA256 ?: @"",
|
||||
kFilePath : wc.event.filePath ?: @"",
|
||||
kFileBundleName : wc.event.fileBundleName ?: @"",
|
||||
kFileBundleID : wc.event.fileBundleID ?: @"",
|
||||
kFileBundleVersion : wc.event.fileBundleVersion ?: @"",
|
||||
kFileBundleShortVersionString : wc.event.fileBundleVersionString ?: @"",
|
||||
kTeamID : wc.event.teamID ?: @"",
|
||||
kExecutingUser : wc.event.executingUser ?: @"",
|
||||
kExecutionTime : @([wc.event.occurrenceDate timeIntervalSince1970]) ?: @0,
|
||||
kPID : wc.event.pid ?: @0,
|
||||
kPPID : wc.event.ppid ?: @0,
|
||||
kParentName : wc.event.parentName ?: @"",
|
||||
kSigningChain : signingChain,
|
||||
};
|
||||
|
||||
[dc postNotificationName:@"com.google.santa.notification.blockedeexecution"
|
||||
object:@"com.google.santa"
|
||||
userInfo:userInfo];
|
||||
}
|
||||
|
||||
- (void)showQueuedWindow {
|
||||
// Notifications arrive on a background thread but UI updates must happen on the main thread.
|
||||
// This includes making windows.
|
||||
@@ -209,31 +257,66 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
#pragma mark SNTNotifierXPC protocol methods
|
||||
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode {
|
||||
NSUserNotification *un = [[NSUserNotification alloc] init];
|
||||
un.title = @"Santa";
|
||||
un.hasActionButton = NO;
|
||||
NSString *customMsg;
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
|
||||
UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = @"Santa";
|
||||
|
||||
switch (clientmode) {
|
||||
case SNTClientModeMonitor:
|
||||
un.informativeText = @"Switching into Monitor mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
|
||||
case SNTClientModeMonitor: {
|
||||
content.body = @"Switching into Monitor mode";
|
||||
NSString *customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
|
||||
if (!customMsg) break;
|
||||
// If a custom message is added but as an empty string, disable notifications.
|
||||
if (!customMsg.length) return;
|
||||
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
|
||||
content.body = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
break;
|
||||
case SNTClientModeLockdown:
|
||||
un.informativeText = @"Switching into Lockdown mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
|
||||
}
|
||||
case SNTClientModeLockdown: {
|
||||
content.body = @"Switching into Lockdown mode";
|
||||
NSString *customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
|
||||
if (!customMsg) break;
|
||||
// If a custom message is added but as an empty string, disable notifications.
|
||||
if (!customMsg.length) return;
|
||||
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
|
||||
content.body = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
break;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
|
||||
UNNotificationRequest *req =
|
||||
[UNNotificationRequest requestWithIdentifier:@"clientModeNotification"
|
||||
content:content
|
||||
trigger:nil];
|
||||
|
||||
[un addNotificationRequest:req withCompletionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
|
||||
UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = @"Santa";
|
||||
content.body = message ?: @"Requested application can now be run";
|
||||
|
||||
NSString *identifier = [NSString stringWithFormat:@"ruleSyncNotification_%@", content.body];
|
||||
|
||||
UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:identifier
|
||||
content:content
|
||||
trigger:nil];
|
||||
|
||||
[un addNotificationRequest:req withCompletionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
@@ -245,15 +328,9 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
|
||||
NSUserNotification *un = [[NSUserNotification alloc] init];
|
||||
un.title = @"Santa";
|
||||
un.hasActionButton = NO;
|
||||
un.informativeText = message ?: @"Requested application can now be run";
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
}
|
||||
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
74
Source/gui/SNTNotificationManagerTest.m
Normal file
@@ -0,0 +1,74 @@
|
||||
/// 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 <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/gui/SNTNotificationManager.h"
|
||||
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@class SNTBinaryMessageWindowController;
|
||||
|
||||
@interface SNTNotificationManager (Testing)
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
|
||||
withController:(SNTBinaryMessageWindowController *)controller;
|
||||
@end
|
||||
|
||||
@interface SNTNotificationManagerTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTNotificationManagerTest
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
fclose(stdout);
|
||||
}
|
||||
|
||||
- (void)testPostBlockNotificationSendsDistributedNotification {
|
||||
SNTStoredEvent *ev = [[SNTStoredEvent alloc] init];
|
||||
ev.fileSHA256 = @"the-sha256";
|
||||
ev.filePath = @"/Applications/Safari.app/Contents/MacOS/Safari";
|
||||
ev.fileBundleName = @"Safari";
|
||||
ev.fileBundlePath = @"/Applications/Safari.app";
|
||||
ev.fileBundleID = @"com.apple.Safari";
|
||||
ev.fileBundleVersion = @"18614.1.14.1.15";
|
||||
ev.fileBundleVersionString = @"16.0";
|
||||
ev.executingUser = @"rah";
|
||||
ev.occurrenceDate = [NSDate dateWithTimeIntervalSince1970:1660221048];
|
||||
ev.decision = SNTEventStateBlockBinary;
|
||||
ev.pid = @84156;
|
||||
ev.ppid = @1;
|
||||
ev.parentName = @"launchd";
|
||||
|
||||
SNTNotificationManager *sut = OCMPartialMock([[SNTNotificationManager alloc] init]);
|
||||
OCMStub([sut hashBundleBinariesForEvent:OCMOCK_ANY withController:OCMOCK_ANY]).andDo(nil);
|
||||
|
||||
id dncMock = OCMClassMock([NSDistributedNotificationCenter class]);
|
||||
OCMStub([dncMock defaultCenter]).andReturn(dncMock);
|
||||
|
||||
[sut postBlockNotification:ev withCustomMessage:@""];
|
||||
|
||||
OCMVerify([dncMock postNotificationName:@"com.google.santa.notification.blockedeexecution"
|
||||
object:@"com.google.santa"
|
||||
userInfo:[OCMArg checkWithBlock:^BOOL(NSDictionary *userInfo) {
|
||||
XCTAssertEqualObjects(userInfo[@"file_sha256"], @"the-sha256");
|
||||
XCTAssertEqualObjects(userInfo[@"pid"], @84156);
|
||||
XCTAssertEqualObjects(userInfo[@"ppid"], @1);
|
||||
XCTAssertEqualObjects(userInfo[@"execution_time"], @1660221048);
|
||||
return YES;
|
||||
}]]);
|
||||
}
|
||||
|
||||
@end
|
||||
8
Source/gui/Santa.app-adhoc.entitlements
Normal file
@@ -0,0 +1,8 @@
|
||||
<?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>com.apple.developer.system-extension.install</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santa/SNTAppDelegate.h"
|
||||
#import "Source/gui/SNTAppDelegate.h"
|
||||
|
||||
@interface SNTSystemExtensionDelegate : NSObject <OSSystemExtensionRequestDelegate>
|
||||
@end
|
||||
@@ -1,16 +0,0 @@
|
||||
<?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>com.apple.application-identifier</key>
|
||||
<string>EQHXZ8M8AV.com.google.santa</string>
|
||||
<key>com.apple.developer.system-extension.install</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>EQHXZ8M8AV</string>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>EQHXZ8M8AV.com.google.santa</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -2,6 +2,10 @@ load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_applicatio
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santabs_lib",
|
||||
srcs = [
|
||||
@@ -12,6 +16,7 @@ objc_library(
|
||||
deps = [
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTXPCBundleServiceInterface",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"@FMDB",
|
||||
@@ -31,8 +36,8 @@ macos_command_line_application(
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.15",
|
||||
provisioning_profile = select({
|
||||
"//:ci_build": None,
|
||||
"//conditions:default": "Santa_Dev.provisionprofile",
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
|
||||
@@ -5,16 +5,38 @@ licenses(["notice"])
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["-layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santactl_cmd",
|
||||
srcs = [
|
||||
"SNTCommand.m",
|
||||
"SNTCommandController.m",
|
||||
],
|
||||
hdrs = [
|
||||
"SNTCommand.h",
|
||||
"SNTCommandController.h",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommandPrintLog",
|
||||
srcs = ["Commands/SNTCommandPrintLog.mm"],
|
||||
deps = [
|
||||
":santactl_cmd",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:santa_cc_proto_library_wrapper",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:binaryproto_cc_proto_library_wrapper",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santactl_lib",
|
||||
srcs = [
|
||||
"SNTCommand.h",
|
||||
"SNTCommand.m",
|
||||
"SNTCommandController.h",
|
||||
"SNTCommandController.m",
|
||||
"main.m",
|
||||
"Commands/SNTCommandFileInfo.m",
|
||||
"Commands/SNTCommandRule.m",
|
||||
@@ -27,7 +49,6 @@ objc_library(
|
||||
"//:opt_build": [],
|
||||
"//conditions:default": [
|
||||
"Commands/SNTCommandBundleInfo.m",
|
||||
"Commands/SNTCommandCacheHistogram.m",
|
||||
"Commands/SNTCommandCheckCache.m",
|
||||
"Commands/SNTCommandFlushCache.m",
|
||||
],
|
||||
@@ -35,6 +56,8 @@ objc_library(
|
||||
sdk_dylibs = ["libz"],
|
||||
sdk_frameworks = ["IOKit"],
|
||||
deps = [
|
||||
":SNTCommandPrintLog",
|
||||
":santactl_cmd",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
@@ -53,6 +76,7 @@ objc_library(
|
||||
"//Source/common:SNTXPCUnprivilegedControlInterface",
|
||||
"//Source/santasyncservice:sync_lib",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -69,8 +93,8 @@ macos_command_line_application(
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.15",
|
||||
provisioning_profile = select({
|
||||
"//:ci_build": None,
|
||||
"//conditions:default": "Santa_Dev.provisionprofile",
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
deps = [":santactl_lib"],
|
||||
@@ -90,6 +114,7 @@ santa_unit_test(
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
@@ -100,12 +125,22 @@ santa_unit_test(
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTCommandMetricsTest",
|
||||
srcs = ["Commands/SNTCommandMetricsTest.m"],
|
||||
srcs = [
|
||||
"Commands/SNTCommandMetrics.h",
|
||||
"Commands/SNTCommandMetricsTest.m",
|
||||
"SNTCommand.h",
|
||||
"SNTCommandController.h",
|
||||
],
|
||||
structured_resources = glob(["Commands/testdata/*"]),
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [
|
||||
":santactl_lib",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
|
||||
"@MOLXPCConnection",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@interface SNTCommandCacheHistogram : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandCacheHistogram
|
||||
|
||||
REGISTER_COMMAND_NAME(@"cachehistogram")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Print a cache distribution histogram.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"Prints a histogram of each bucket of the in-kernel cache\n"
|
||||
@" Use -g to get 'graphical' output\n"
|
||||
@"Only available in DEBUG builds.");
|
||||
}
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
[[self.daemonConn remoteObjectProxy] cacheBucketCount:^(NSArray *counts) {
|
||||
NSMutableDictionary<NSNumber *, NSNumber *> *d = [NSMutableDictionary dictionary];
|
||||
[counts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
|
||||
d[obj] = @([d[obj] intValue] + 1);
|
||||
}];
|
||||
printf("There are %llu empty buckets\n", [d[@0] unsignedLongLongValue]);
|
||||
|
||||
for (NSNumber *key in [d.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
if ([key isEqual:@0]) continue;
|
||||
uint64_t k = [key unsignedLongLongValue];
|
||||
uint64_t v = [d[key] unsignedLongLongValue];
|
||||
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"-g"]) {
|
||||
printf("%4llu: ", k);
|
||||
for (uint64_t y = 0; y < v; ++y) {
|
||||
printf("#");
|
||||
}
|
||||
printf("\n");
|
||||
} else {
|
||||
printf("%4llu bucket[s] have %llu %s\n", v, k, k > 1 ? "entries" : "entry");
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -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.
|
||||
@@ -62,9 +62,6 @@ REGISTER_COMMAND_NAME(@"checkcache")
|
||||
} else if (action == ACTION_RESPOND_ALLOW_COMPILER) {
|
||||
LOGI(@"File exists in [allowlist compiler] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE) {
|
||||
LOGI(@"File exists in [allowlist pending_transitive] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_UNSET) {
|
||||
LOGE(@"File does not exist in cache");
|
||||
exit(1);
|
||||
|
||||
@@ -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.
|
||||
@@ -385,6 +385,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
|
||||
case SNTEventStateAllowCompiler: [output appendString:@" (Compiler)"]; break;
|
||||
case SNTEventStateAllowTransitive: [output appendString:@" (Transitive)"]; break;
|
||||
case SNTEventStateBlockLongPath: [output appendString:@" (Long Path)"]; break;
|
||||
|
||||
default: output = @"None".mutableCopy; break;
|
||||
}
|
||||
|
||||
@@ -68,14 +68,34 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
|
||||
for (NSString *fieldName in metric[@"fields"]) {
|
||||
for (NSDictionary *field in metric[@"fields"][fieldName]) {
|
||||
const char *fieldNameStr = [fieldName cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
const char *fieldValueStr = [field[@"value"] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
const char *createdStr = [field[@"created"] UTF8String];
|
||||
const char *lastUpdatedStr = [field[@"last_updated"] UTF8String];
|
||||
const char *data = [[NSString stringWithFormat:@"%@", field[@"data"]] UTF8String];
|
||||
|
||||
if (strlen(fieldNameStr) > 0) {
|
||||
printf(" %-25s | %s=%s\n", "Field", fieldNameStr, fieldValueStr);
|
||||
NSArray<NSString *> *fields = [fieldName componentsSeparatedByString:@","];
|
||||
NSArray<NSString *> *fieldValues = [field[@"value"] componentsSeparatedByString:@","];
|
||||
|
||||
if (fields.count != fieldValues.count) {
|
||||
fprintf(stderr, "metric %s has a different number of field names and field values",
|
||||
[fieldName UTF8String]);
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *fieldDisplayString = @"";
|
||||
|
||||
if (fields.count >= 1 && fields[0].length) {
|
||||
for (int i = 0; i < fields.count; i++) {
|
||||
fieldDisplayString = [fieldDisplayString
|
||||
stringByAppendingString:[NSString
|
||||
stringWithFormat:@"%@=%@", fields[i], fieldValues[i]]];
|
||||
if (i < fields.count - 1) {
|
||||
fieldDisplayString = [fieldDisplayString stringByAppendingString:@","];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (![fieldDisplayString isEqualToString:@""]) {
|
||||
printf(" %-25s | %s\n", "Field", [fieldDisplayString UTF8String]);
|
||||
}
|
||||
|
||||
printf(" %-25s | %s\n", "Created", createdStr);
|
||||
@@ -119,7 +139,7 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
printf(">>> Root Labels\n");
|
||||
[self prettyPrintRootLabels:normalizedMetrics[@"root_labels"]];
|
||||
printf("\n");
|
||||
printf(">>> Metrics \n");
|
||||
printf(">>> Metrics\n");
|
||||
[self prettyPrintMetricValues:normalizedMetrics[@"metrics"]];
|
||||
}
|
||||
|
||||
|
||||
131
Source/santactl/Commands/SNTCommandPrintLog.mm
Normal file
@@ -0,0 +1,131 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <google/protobuf/util/json_util.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/common/santa_proto_include_wrapper.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
#include "Source/santad/Logs/EndpointSecurity/Writers/FSSpool/binaryproto_proto_include_wrapper.h"
|
||||
#include "google/protobuf/any.pb.h"
|
||||
|
||||
using google::protobuf::util::JsonPrintOptions;
|
||||
using google::protobuf::util::MessageToJsonString;
|
||||
using santa::fsspool::binaryproto::LogBatch;
|
||||
namespace pbv1 = ::santa::pb::v1;
|
||||
|
||||
@interface SNTCommandPrintLog : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandPrintLog
|
||||
|
||||
REGISTER_COMMAND_NAME(@"printlog")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Prints the contents of Santa protobuf log files as JSON.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return @"Prints the contents of serialized Santa protobuf logs as JSON.\n"
|
||||
@"Multiple paths can be provided. The output is a list of all the \n"
|
||||
@"SantaMessage entries per-file. E.g.: \n"
|
||||
@" [\n"
|
||||
@" [\n"
|
||||
@" ... file 1 contents ...\n"
|
||||
@" ],\n"
|
||||
@" [\n"
|
||||
@" ... file N contents ...\n"
|
||||
@" ]\n"
|
||||
@" ]";
|
||||
}
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
JsonPrintOptions options;
|
||||
options.always_print_enums_as_ints = false;
|
||||
options.always_print_primitive_fields = true;
|
||||
options.preserve_proto_field_names = true;
|
||||
options.add_whitespace = true;
|
||||
|
||||
for (int argIdx = 0; argIdx < [arguments count]; argIdx++) {
|
||||
NSString *path = arguments[argIdx];
|
||||
int fd = open([path UTF8String], O_RDONLY);
|
||||
if (fd == -1) {
|
||||
LOGE(@"Failed to open '%@': errno: %d: %s", path, errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
LogBatch logBatch;
|
||||
bool ret = logBatch.ParseFromFileDescriptor(fd);
|
||||
close(fd);
|
||||
|
||||
if (!ret) {
|
||||
LOGE(@"Failed to parse '%@'", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argIdx != 0) {
|
||||
std::cout << ",";
|
||||
} else {
|
||||
// Print the opening outer JSON array
|
||||
std::cout << "[";
|
||||
}
|
||||
std::cout << "\n[\n";
|
||||
|
||||
int numRecords = logBatch.records_size();
|
||||
|
||||
for (int i = 0; i < numRecords; i++) {
|
||||
const google::protobuf::Any &any = logBatch.records(i);
|
||||
::pbv1::SantaMessage santaMsg;
|
||||
if (!any.UnpackTo(&santaMsg)) {
|
||||
LOGE(@"Failed to unpack Any proto to SantaMessage in file '%@'", path);
|
||||
break;
|
||||
}
|
||||
|
||||
if (i != 0) {
|
||||
std::cout << ",\n";
|
||||
}
|
||||
|
||||
std::string json;
|
||||
if (!MessageToJsonString(santaMsg, &json, options).ok()) {
|
||||
LOGE(@"Unable to convert message to JSON in file: '%@'", path);
|
||||
}
|
||||
std::cout << json;
|
||||
}
|
||||
|
||||
std::cout << "]" << std::flush;
|
||||
|
||||
if (argIdx == ([arguments count] - 1)) {
|
||||
// Print the closing outer JSON array
|
||||
std::cout << "]\n";
|
||||
}
|
||||
}
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -74,14 +74,15 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
// DEBUG builds add a --force flag to allow manually adding/removing rules during testing.
|
||||
if ((config.syncBaseURL || config.staticRules.count) &&
|
||||
![arguments containsObject:@"--check"]
|
||||
#ifdef DEBUG
|
||||
if ([config syncBaseURL] && ![arguments containsObject:@"--check"] &&
|
||||
![arguments containsObject:@"--force"]) {
|
||||
// DEBUG builds add a --force flag to allow manually adding/removing rules during testing.
|
||||
&& ![arguments containsObject:@"--force"]) {
|
||||
#else
|
||||
if ([config syncBaseURL] && ![arguments containsObject:@"--check"]) {
|
||||
) {
|
||||
#endif
|
||||
printf("SyncBaseURL is set, rules are managed centrally.\n");
|
||||
printf("(SyncBaseURL/StaticRules is set, rules are managed centrally.)");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -48,16 +48,10 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
// Daemon status
|
||||
__block BOOL driverConnected;
|
||||
__block NSString *clientMode;
|
||||
__block uint64_t cpuEvents, ramEvents;
|
||||
__block double cpuPeak, ramPeak;
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] driverConnectionEstablished:^(BOOL connected) {
|
||||
driverConnected = connected;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case SNTClientModeMonitor: clientMode = @"Monitor"; break;
|
||||
@@ -80,18 +74,14 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
|
||||
SNTConfigurator *configurator = [SNTConfigurator configurator];
|
||||
|
||||
BOOL cachingEnabled = [configurator enableSysxCache];
|
||||
|
||||
// Cache status
|
||||
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
|
||||
if (cachingEnabled) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] cacheCounts:^(uint64_t rootCache, uint64_t nonRootCache) {
|
||||
rootCacheCount = rootCache;
|
||||
nonRootCacheCount = nonRootCache;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
}
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] cacheCounts:^(uint64_t rootCache, uint64_t nonRootCache) {
|
||||
rootCacheCount = rootCache;
|
||||
nonRootCacheCount = nonRootCache;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
// Database counts
|
||||
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1, teamIDRuleCount = -1;
|
||||
@@ -113,6 +103,14 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
// Static rule count
|
||||
__block int64_t staticRuleCount = -1;
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] staticRuleCount:^(int64_t count) {
|
||||
staticRuleCount = count;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
// Sync status
|
||||
__block NSDate *fullSyncLastSuccess;
|
||||
dispatch_group_enter(group);
|
||||
@@ -172,16 +170,16 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
NSString *ruleSyncLastSuccessStr =
|
||||
[dateFormatter stringFromDate:ruleSyncLastSuccess] ?: fullSyncLastSuccessStr;
|
||||
|
||||
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
|
||||
NSString *syncURLStr = configurator.syncBaseURL.absoluteString;
|
||||
|
||||
BOOL exportMetrics = [[SNTConfigurator configurator] exportMetrics];
|
||||
NSURL *metricsURLStr = [[SNTConfigurator configurator] metricURL];
|
||||
NSUInteger metricExportInterval = [[SNTConfigurator configurator] metricExportInterval];
|
||||
BOOL exportMetrics = configurator.exportMetrics;
|
||||
NSURL *metricsURLStr = configurator.metricURL;
|
||||
NSUInteger metricExportInterval = configurator.metricExportInterval;
|
||||
|
||||
if ([arguments containsObject:@"--json"]) {
|
||||
NSMutableDictionary *stats = [@{
|
||||
@"daemon" : @{
|
||||
@"driver_connected" : @(driverConnected),
|
||||
@"driver_connected" : @(YES),
|
||||
@"mode" : clientMode ?: @"null",
|
||||
@"file_logging" : @(fileLogging),
|
||||
@"watchdog_cpu_events" : @(cpuEvents),
|
||||
@@ -200,6 +198,9 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
@"transitive_rules" : @(transitiveRuleCount),
|
||||
@"events_pending_upload" : @(eventCount),
|
||||
},
|
||||
@"static_rules" : @{
|
||||
@"rule_count" : @(staticRuleCount),
|
||||
},
|
||||
@"sync" : @{
|
||||
@"server" : syncURLStr ?: @"null",
|
||||
@"clean_required" : @(syncCleanReqd),
|
||||
@@ -210,12 +211,12 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
@"transitive_rules" : @(enableTransitiveRules),
|
||||
},
|
||||
} mutableCopy];
|
||||
if (cachingEnabled) {
|
||||
stats[@"cache"] = @{
|
||||
@"root_cache_count" : @(rootCacheCount),
|
||||
@"non_root_cache_count" : @(nonRootCacheCount),
|
||||
};
|
||||
}
|
||||
|
||||
stats[@"cache"] = @{
|
||||
@"root_cache_count" : @(rootCacheCount),
|
||||
@"non_root_cache_count" : @(nonRootCacheCount),
|
||||
};
|
||||
|
||||
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:nil];
|
||||
@@ -223,22 +224,19 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf("%s\n", [statsStr UTF8String]);
|
||||
} else {
|
||||
printf(">>> Daemon Info\n");
|
||||
printf(" %-25s | %s\n", "Driver Connected", driverConnected ? "Yes" : "No");
|
||||
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
|
||||
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
|
||||
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
|
||||
printf(" %-25s | %s\n", "USB Remounting Mode:",
|
||||
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
|
||||
}
|
||||
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
|
||||
if (cachingEnabled) {
|
||||
printf(">>> Cache Info\n");
|
||||
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
|
||||
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
|
||||
}
|
||||
printf(">>> Cache Info\n");
|
||||
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
|
||||
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
|
||||
|
||||
printf(">>> Database Info\n");
|
||||
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
|
||||
@@ -248,6 +246,11 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf(" %-25s | %lld\n", "Transitive Rules", transitiveRuleCount);
|
||||
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
|
||||
|
||||
if ([SNTConfigurator configurator].staticRules.count) {
|
||||
printf(">>> Static Rules\n");
|
||||
printf(" %-25s | %lld\n", "Rules", staticRuleCount);
|
||||
}
|
||||
|
||||
if (syncURLStr) {
|
||||
printf(">>> Sync Info\n");
|
||||
printf(" %-25s | %s\n", "Sync Server", [syncURLStr UTF8String]);
|
||||
|
||||
@@ -38,18 +38,18 @@
|
||||
"type" : 9,
|
||||
"description" : "Count of process exec events on the host",
|
||||
"fields" : {
|
||||
"rule_type" : [
|
||||
"rule_type,client" : [
|
||||
{
|
||||
"created" : "2021-09-16T21:07:34.826Z",
|
||||
"last_updated" : "2021-09-16T21:07:34.826Z",
|
||||
"value" : "binary",
|
||||
"data" : 1
|
||||
"value" : "certificate,authorizer",
|
||||
"data" : 2
|
||||
},
|
||||
{
|
||||
"created" : "2021-09-16T21:07:34.826Z",
|
||||
"last_updated" : "2021-09-16T21:07:34.826Z",
|
||||
"value" : "certificate",
|
||||
"data" : 2
|
||||
"value" : "binary,authorizer",
|
||||
"data" : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
hostname | testHost
|
||||
username | testUser
|
||||
|
||||
>>> Metrics
|
||||
>>> Metrics
|
||||
Metric Name | /santa/rules
|
||||
Description | Number of rules
|
||||
Type | SNTMetricTypeGaugeInt64
|
||||
@@ -30,14 +30,14 @@
|
||||
Metric Name | /santa/events
|
||||
Description | Count of process exec events on the host
|
||||
Type | SNTMetricTypeCounter
|
||||
Field | rule_type=binary
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 1
|
||||
Field | rule_type=certificate
|
||||
Field | rule_type=certificate,client=authorizer
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 2
|
||||
Field | rule_type=binary,client=authorizer
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 1
|
||||
|
||||
Metric Name | /santa/using_endpoint_security_framework
|
||||
Description | Is santad using the endpoint security framework
|
||||
|
||||
1284
Source/santad/BUILD
@@ -105,6 +105,7 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
|
||||
// This is a Santa-curated list of paths to check on startup. This list will be merged
|
||||
// with the set of default muted paths from ES.
|
||||
|
||||
NSSet *santaDefinedCriticalPaths = [NSSet setWithArray:@[
|
||||
@"/usr/libexec/trustd",
|
||||
@"/usr/lib/dyld",
|
||||
@@ -136,6 +137,12 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
NSMutableDictionary *bins = [NSMutableDictionary dictionary];
|
||||
for (NSString *path in [SNTRuleTable criticalSystemBinaryPaths]) {
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:path];
|
||||
if (!binInfo.SHA256) {
|
||||
// If there isn't a hash, no need to compute the other info here.
|
||||
// Just continue on to the next binary.
|
||||
LOGW(@"Unable to compute hash for critical system binary %@.", path);
|
||||
continue;
|
||||
}
|
||||
MOLCodesignChecker *csInfo = [binInfo codesignCheckerWithError:NULL];
|
||||
|
||||
// Make sure the critical system binary is signed by the same chain as launchd/self
|
||||
@@ -143,9 +150,9 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
if ([csInfo signingInformationMatches:self.launchdCSInfo]) {
|
||||
systemBin = YES;
|
||||
} else if (![csInfo signingInformationMatches:self.santadCSInfo]) {
|
||||
LOGE(@"Unable to validate critical system binary. "
|
||||
LOGW(@"Unable to validate critical system binary %@. "
|
||||
@"pid 1: %@, santad: %@ and %@: %@ do not match.",
|
||||
self.launchdCSInfo.leafCertificate, self.santadCSInfo.leafCertificate, path,
|
||||
path, self.launchdCSInfo.leafCertificate, self.santadCSInfo.leafCertificate, path,
|
||||
csInfo.leafCertificate);
|
||||
continue;
|
||||
}
|
||||
@@ -277,6 +284,19 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
teamID:(NSString *)teamID {
|
||||
__block SNTRule *rule;
|
||||
|
||||
// Look for a static rule that matches.
|
||||
NSDictionary *staticRules = [[SNTConfigurator configurator] staticRules];
|
||||
if (staticRules.count) {
|
||||
rule = staticRules[binarySHA256];
|
||||
if (rule.type == SNTRuleTypeBinary) return rule;
|
||||
rule = staticRules[certificateSHA256];
|
||||
if (rule.type == SNTRuleTypeCertificate) return rule;
|
||||
rule = staticRules[teamID];
|
||||
if (rule.type == SNTRuleTypeTeamID) return rule;
|
||||
}
|
||||
|
||||
// Now query the database.
|
||||
//
|
||||
// NOTE: This code is written with the intention that the binary rule is searched for first
|
||||
// as Santa is designed to go with the most-specific rule possible.
|
||||
//
|
||||
|
||||
75
Source/santad/EventProviders/AuthResultCache.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__SANTAD__EVENTPROVIDERS_AUTHRESULTCACHE_H
|
||||
#define SANTA__SANTAD__EVENTPROVIDERS_AUTHRESULTCACHE_H
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <sys/stat.h>
|
||||
#include <memory>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
|
||||
enum class FlushCacheMode {
|
||||
kNonRootOnly,
|
||||
kAllCaches,
|
||||
};
|
||||
|
||||
class AuthResultCache {
|
||||
public:
|
||||
// Santa currently only flushes caches when new DENY rules are added, not
|
||||
// ALLOW rules. This means this value should be low enough so that if a
|
||||
// previously denied binary is allowed, it can be re-executed by the user in a
|
||||
// timely manner. But the value should be high enough to allow the cache to be
|
||||
// effective in the event the binary is executed in rapid succession.
|
||||
AuthResultCache(
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
|
||||
uint64_t cache_deny_time_ms = 1500);
|
||||
virtual ~AuthResultCache();
|
||||
|
||||
AuthResultCache(AuthResultCache &&other) = delete;
|
||||
AuthResultCache &operator=(AuthResultCache &&rhs) = delete;
|
||||
AuthResultCache(const AuthResultCache &other) = delete;
|
||||
AuthResultCache &operator=(const AuthResultCache &other) = delete;
|
||||
|
||||
virtual bool AddToCache(const es_file_t *es_file, santa_action_t decision);
|
||||
virtual void RemoveFromCache(const es_file_t *es_file);
|
||||
virtual santa_action_t CheckCache(const es_file_t *es_file);
|
||||
virtual santa_action_t CheckCache(santa_vnode_id_t vnode_id);
|
||||
|
||||
virtual void FlushCache(FlushCacheMode mode);
|
||||
|
||||
virtual NSArray<NSNumber *> *CacheCounts();
|
||||
|
||||
private:
|
||||
virtual SantaCache<santa_vnode_id_t, uint64_t> *CacheForVnodeID(santa_vnode_id_t vnode_id);
|
||||
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *root_cache_;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *nonroot_cache_;
|
||||
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi_;
|
||||
uint64_t root_devno_;
|
||||
uint64_t cache_deny_time_ns_;
|
||||
dispatch_queue_t q_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers
|
||||
|
||||
#endif
|
||||
156
Source/santad/EventProviders/AuthResultCache.mm
Normal file
@@ -0,0 +1,156 @@
|
||||
|
||||
/// 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/santad/EventProviders/AuthResultCache.h"
|
||||
|
||||
#include <mach/clock_types.h>
|
||||
#include <time.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
|
||||
}
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
|
||||
static inline santa_vnode_id_t VnodeForFile(const es_file_t *es_file) {
|
||||
return santa_vnode_id_t{
|
||||
.fsid = (uint64_t)es_file->stat.st_dev,
|
||||
.fileid = es_file->stat.st_ino,
|
||||
};
|
||||
}
|
||||
|
||||
static inline uint64_t GetCurrentUptime() {
|
||||
return clock_gettime_nsec_np(CLOCK_MONOTONIC);
|
||||
}
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
static inline uint64_t CacheableAction(santa_action_t action,
|
||||
uint64_t timestamp = GetCurrentUptime()) {
|
||||
return ((uint64_t)action << 56) | (timestamp & 0xFFFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
static inline santa_action_t ActionFromCachedValue(uint64_t cachedValue) {
|
||||
return (santa_action_t)(cachedValue >> 56);
|
||||
}
|
||||
|
||||
static inline uint64_t TimestampFromCachedValue(uint64_t cachedValue) {
|
||||
return (cachedValue & ~(0xFF00000000000000));
|
||||
}
|
||||
|
||||
AuthResultCache::AuthResultCache(std::shared_ptr<EndpointSecurityAPI> esapi,
|
||||
uint64_t cache_deny_time_ms)
|
||||
: esapi_(esapi), cache_deny_time_ns_(cache_deny_time_ms * NSEC_PER_MSEC) {
|
||||
root_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
nonroot_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
|
||||
struct stat sb;
|
||||
if (stat("/", &sb) == 0) {
|
||||
root_devno_ = sb.st_dev;
|
||||
}
|
||||
|
||||
q_ = dispatch_queue_create(
|
||||
"com.google.santa.daemon.auth_result_cache.q",
|
||||
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL,
|
||||
QOS_CLASS_USER_INTERACTIVE, 0));
|
||||
}
|
||||
|
||||
AuthResultCache::~AuthResultCache() {
|
||||
delete root_cache_;
|
||||
delete nonroot_cache_;
|
||||
}
|
||||
|
||||
bool AuthResultCache::AddToCache(const es_file_t *es_file, santa_action_t decision) {
|
||||
santa_vnode_id_t vnode_id = VnodeForFile(es_file);
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *cache = CacheForVnodeID(vnode_id);
|
||||
switch (decision) {
|
||||
case ACTION_REQUEST_BINARY:
|
||||
return cache->set(vnode_id, CacheableAction(ACTION_REQUEST_BINARY, 0), 0);
|
||||
case ACTION_RESPOND_ALLOW: OS_FALLTHROUGH;
|
||||
case ACTION_RESPOND_ALLOW_COMPILER: OS_FALLTHROUGH;
|
||||
case ACTION_RESPOND_DENY:
|
||||
return cache->set(vnode_id, CacheableAction(decision),
|
||||
CacheableAction(ACTION_REQUEST_BINARY, 0));
|
||||
default:
|
||||
// This is a programming error. Bail.
|
||||
LOGE(@"Invalid cache value, exiting.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void AuthResultCache::RemoveFromCache(const es_file_t *es_file) {
|
||||
santa_vnode_id_t vnode_id = VnodeForFile(es_file);
|
||||
CacheForVnodeID(vnode_id)->remove(vnode_id);
|
||||
}
|
||||
|
||||
santa_action_t AuthResultCache::CheckCache(const es_file_t *es_file) {
|
||||
return CheckCache(VnodeForFile(es_file));
|
||||
}
|
||||
|
||||
santa_action_t AuthResultCache::CheckCache(santa_vnode_id_t vnode_id) {
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *cache = CacheForVnodeID(vnode_id);
|
||||
|
||||
uint64_t cached_val = cache->get(vnode_id);
|
||||
if (cached_val == 0) {
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
|
||||
santa_action_t result = ActionFromCachedValue(cached_val);
|
||||
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
uint64_t expiry_time = TimestampFromCachedValue(cached_val) + cache_deny_time_ns_;
|
||||
if (expiry_time < GetCurrentUptime()) {
|
||||
cache->remove(vnode_id);
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *AuthResultCache::CacheForVnodeID(
|
||||
santa_vnode_id_t vnode_id) {
|
||||
return (vnode_id.fsid == root_devno_ || root_devno_ == 0) ? root_cache_ : nonroot_cache_;
|
||||
}
|
||||
|
||||
void AuthResultCache::FlushCache(FlushCacheMode mode) {
|
||||
nonroot_cache_->clear();
|
||||
if (mode == FlushCacheMode::kAllCaches) {
|
||||
root_cache_->clear();
|
||||
|
||||
// Clear the ES cache when all local caches are flushed. Assume the ES cache
|
||||
// doesn't need to be cleared when only flushing the non-root cache.
|
||||
//
|
||||
// Calling into ES should be done asynchronously since it could otherwise
|
||||
// potentially deadlock.
|
||||
auto shared_esapi = esapi_->shared_from_this();
|
||||
dispatch_async(q_, ^{
|
||||
// ES does not need a connected client to clear cache
|
||||
shared_esapi->ClearCache(Client());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
NSArray<NSNumber *> *AuthResultCache::CacheCounts() {
|
||||
return @[ @(root_cache_->count()), @(nonroot_cache_->count()) ];
|
||||
}
|
||||
|
||||
} // namespace santa::santad::event_providers
|
||||
225
Source/santad/EventProviders/AuthResultCacheTest.mm
Normal file
@@ -0,0 +1,225 @@
|
||||
/// 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 <EndpointSecurity/EndpointSecurity.h>
|
||||
#include <Foundation/Foundation.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Source/common/SNTCommon.h"
|
||||
#include "Source/common/TestUtils.h"
|
||||
#include "Source/santad/EventProviders/AuthResultCache.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h"
|
||||
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::FlushCacheMode;
|
||||
|
||||
// Grab the st_dev number of the root volume to match the root cache
|
||||
static uint64_t RootDevno() {
|
||||
static dispatch_once_t once_token;
|
||||
static uint64_t devno;
|
||||
dispatch_once(&once_token, ^{
|
||||
struct stat sb;
|
||||
stat("/", &sb);
|
||||
devno = sb.st_dev;
|
||||
});
|
||||
return devno;
|
||||
}
|
||||
|
||||
static inline es_file_t MakeCacheableFile(uint64_t devno, uint64_t ino) {
|
||||
return es_file_t{
|
||||
.path = {}, .path_truncated = false, .stat = {.st_dev = (dev_t)devno, .st_ino = ino}};
|
||||
}
|
||||
|
||||
static inline santa_vnode_id_t VnodeForFile(const es_file_t *es_file) {
|
||||
return santa_vnode_id_t{
|
||||
.fsid = (uint64_t)es_file->stat.st_dev,
|
||||
.fileid = es_file->stat.st_ino,
|
||||
};
|
||||
}
|
||||
|
||||
static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uint64_t root_count,
|
||||
uint64_t nonroot_count) {
|
||||
NSArray<NSNumber *> *counts = cache->CacheCounts();
|
||||
|
||||
XCTAssertNotNil(counts);
|
||||
XCTAssertEqual([counts count], 2);
|
||||
XCTAssertNotNil(counts[0]);
|
||||
XCTAssertNotNil(counts[1]);
|
||||
XCTAssertEqual([counts[0] unsignedLongLongValue], root_count);
|
||||
XCTAssertEqual([counts[1] unsignedLongLongValue], nonroot_count);
|
||||
}
|
||||
|
||||
@interface AuthResultCacheTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation AuthResultCacheTest
|
||||
|
||||
- (void)testEmptyCacheExpectedNumberOfCacheCounts {
|
||||
auto esapi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
auto cache = std::make_shared<AuthResultCache>(esapi);
|
||||
|
||||
AssertCacheCounts(cache, 0, 0);
|
||||
}
|
||||
|
||||
- (void)testBasicOperation {
|
||||
auto esapi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
auto cache = std::make_shared<AuthResultCache>(esapi);
|
||||
|
||||
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
|
||||
es_file_t nonrootFile = MakeCacheableFile(RootDevno() + 123, 222);
|
||||
|
||||
// Add the root file to the cache
|
||||
cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY);
|
||||
|
||||
AssertCacheCounts(cache, 1, 0);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), ACTION_UNSET);
|
||||
|
||||
// Now add the non-root file
|
||||
cache->AddToCache(&nonrootFile, ACTION_REQUEST_BINARY);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), ACTION_REQUEST_BINARY);
|
||||
|
||||
// Update the cached values
|
||||
cache->AddToCache(&rootFile, ACTION_RESPOND_ALLOW);
|
||||
cache->AddToCache(&nonrootFile, ACTION_RESPOND_DENY);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
XCTAssertEqual(cache->CheckCache(VnodeForFile(&rootFile)), ACTION_RESPOND_ALLOW);
|
||||
XCTAssertEqual(cache->CheckCache(VnodeForFile(&nonrootFile)), ACTION_RESPOND_DENY);
|
||||
|
||||
// Remove the root file
|
||||
cache->RemoveFromCache(&rootFile);
|
||||
|
||||
AssertCacheCounts(cache, 0, 1);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
XCTAssertEqual(cache->CheckCache(&nonrootFile), ACTION_RESPOND_DENY);
|
||||
}
|
||||
|
||||
- (void)testFlushCache {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
auto cache = std::make_shared<AuthResultCache>(mockESApi);
|
||||
|
||||
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
|
||||
es_file_t nonrootFile = MakeCacheableFile(RootDevno() + 123, 111);
|
||||
|
||||
cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY);
|
||||
cache->AddToCache(&nonrootFile, ACTION_REQUEST_BINARY);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
|
||||
// Flush non-root only
|
||||
cache->FlushCache(FlushCacheMode::kNonRootOnly);
|
||||
|
||||
AssertCacheCounts(cache, 1, 0);
|
||||
|
||||
// Add back the non-root file
|
||||
cache->AddToCache(&nonrootFile, ACTION_REQUEST_BINARY);
|
||||
|
||||
AssertCacheCounts(cache, 1, 1);
|
||||
|
||||
// Flush all caches
|
||||
// The call to ClearCache is asynchronous. Use a semaphore to
|
||||
// be notified when the mock is called.
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
EXPECT_CALL(*mockESApi, ClearCache).WillOnce(testing::InvokeWithoutArgs(^() {
|
||||
dispatch_semaphore_signal(sema);
|
||||
return true;
|
||||
}));
|
||||
cache->FlushCache(FlushCacheMode::kAllCaches);
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)),
|
||||
"ClearCache wasn't called within expected time window");
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
|
||||
AssertCacheCounts(cache, 0, 0);
|
||||
}
|
||||
|
||||
- (void)testCacheStateMachine {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
auto cache = std::make_shared<AuthResultCache>(mockESApi);
|
||||
|
||||
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
|
||||
|
||||
// Cached items must first be in the ACTION_REQUEST_BINARY state
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_RESPOND_ALLOW));
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_RESPOND_ALLOW_COMPILER));
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_RESPOND_DENY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
|
||||
// Items in the `ACTION_REQUEST_BINARY` state cannot reenter the same state
|
||||
XCTAssertFalse(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
|
||||
santa_action_t allowed_transitions[] = {
|
||||
ACTION_RESPOND_ALLOW,
|
||||
ACTION_RESPOND_ALLOW_COMPILER,
|
||||
ACTION_RESPOND_DENY,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(allowed_transitions) / sizeof(allowed_transitions[0]); i++) {
|
||||
// First make sure the item doesn't exist
|
||||
cache->RemoveFromCache(&rootFile);
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
|
||||
// Now add the item to be in the first allowed state
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_REQUEST_BINARY);
|
||||
|
||||
// Now assert the allowed transition
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, allowed_transitions[i]));
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), allowed_transitions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testCacheExpiry {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
// Create a cache with a lowered cache expiry value
|
||||
uint64_t expiryMS = 250;
|
||||
auto cache = std::make_shared<AuthResultCache>(mockESApi, expiryMS);
|
||||
|
||||
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
|
||||
|
||||
// Add a file to the cache and put into the ACTION_RESPOND_DENY state
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_REQUEST_BINARY));
|
||||
XCTAssertTrue(cache->AddToCache(&rootFile, ACTION_RESPOND_DENY));
|
||||
|
||||
// Ensure the file exists
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_RESPOND_DENY);
|
||||
|
||||
// Wait for the item to expire
|
||||
SleepMS(expiryMS);
|
||||
|
||||
// Check cache counts to make sure the item still exists
|
||||
AssertCacheCounts(cache, 1, 0);
|
||||
|
||||
// Now check the cache, which will remove the item
|
||||
XCTAssertEqual(cache->CheckCache(&rootFile), ACTION_UNSET);
|
||||
AssertCacheCounts(cache, 0, 0);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
/// Copyright 2021-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.
|
||||
@@ -50,7 +50,8 @@ typedef void (^MockDADiskAppearedCallback)(DADiskRef ref);
|
||||
@end
|
||||
|
||||
//
|
||||
// All DiskArbitration functions used in SNTDeviceManager and shimmed out accordingly.
|
||||
// All DiskArbitration functions used in SNTEndpointSecurityDeviceManager
|
||||
// and shimmed out accordingly.
|
||||
//
|
||||
CF_EXTERN_C_BEGIN
|
||||
|
||||
|
||||
69
Source/santad/EventProviders/EndpointSecurity/Client.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/// 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__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_CLIENT_H
|
||||
#define SANTA__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_CLIENT_H
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
|
||||
class Client {
|
||||
public:
|
||||
explicit Client(es_client_t* client, es_new_client_result_t result)
|
||||
: client_(client), result_(result) {}
|
||||
|
||||
Client() : client_(nullptr), result_(ES_NEW_CLIENT_RESULT_ERR_INTERNAL) {}
|
||||
|
||||
virtual ~Client() {
|
||||
if (client_) {
|
||||
// Special case: Not using EndpointSecurityAPI here due to circular refs.
|
||||
es_delete_client(client_);
|
||||
}
|
||||
}
|
||||
|
||||
Client(Client&& other) {
|
||||
client_ = other.client_;
|
||||
result_ = other.result_;
|
||||
other.client_ = nullptr;
|
||||
other.result_ = ES_NEW_CLIENT_RESULT_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
Client& operator=(Client&& rhs) {
|
||||
client_ = rhs.client_;
|
||||
result_ = rhs.result_;
|
||||
rhs.client_ = nullptr;
|
||||
rhs.result_ = ES_NEW_CLIENT_RESULT_ERR_INTERNAL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client(const Client& other) = delete;
|
||||
void operator=(const Client& rhs) = delete;
|
||||
|
||||
inline bool IsConnected() { return result_ == ES_NEW_CLIENT_RESULT_SUCCESS; }
|
||||
|
||||
inline es_new_client_result_t NewClientResult() { return result_; }
|
||||
|
||||
inline es_client_t* Get() const { return client_; }
|
||||
|
||||
private:
|
||||
es_client_t* client_;
|
||||
es_new_client_result_t result_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
|
||||
#endif
|
||||
118
Source/santad/EventProviders/EndpointSecurity/ClientTest.mm
Normal file
@@ -0,0 +1,118 @@
|
||||
/// 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 <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
|
||||
// Global semaphore used for custom `es_delete_client` function
|
||||
dispatch_semaphore_t gSema;
|
||||
|
||||
// Note: The Client class does not use the `EndpointSecurityAPI` wrappers due
|
||||
// to circular dependency issues. It is a special case that uses the underlying
|
||||
// ES API `es_delete_client` directly. This test override will signal the
|
||||
// `gSema` semaphore to indicate it has been called.
|
||||
es_return_t es_delete_client(es_client_t *_Nullable client) {
|
||||
dispatch_semaphore_signal(gSema);
|
||||
return ES_RETURN_SUCCESS;
|
||||
};
|
||||
|
||||
@interface ClientTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ClientTest
|
||||
|
||||
- (void)setUp {
|
||||
gSema = dispatch_semaphore_create(0);
|
||||
}
|
||||
|
||||
- (void)testConstructorsAndDestructors {
|
||||
// Ensure constructors set internal state properly
|
||||
// Anonymous scopes used to ensure destructors called as expected
|
||||
|
||||
// Null `es_client_t*` *shouldn't* trigger `es_delete_client`
|
||||
{
|
||||
Client c;
|
||||
XCTAssertEqual(c.Get(), nullptr);
|
||||
XCTAssertEqual(c.NewClientResult(), ES_NEW_CLIENT_RESULT_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
XCTAssertNotEqual(0, dispatch_semaphore_wait(gSema, DISPATCH_TIME_NOW),
|
||||
"es_delete_client called unexpectedly");
|
||||
|
||||
// Nonnull `es_client_t*` *should* trigger `es_delete_client`
|
||||
{
|
||||
int fake;
|
||||
es_client_t *fakeClient = (es_client_t *)&fake;
|
||||
Client c(fakeClient, ES_NEW_CLIENT_RESULT_SUCCESS);
|
||||
XCTAssertEqual(c.Get(), fakeClient);
|
||||
XCTAssertEqual(c.NewClientResult(), ES_NEW_CLIENT_RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
XCTAssertEqual(0, dispatch_semaphore_wait(gSema, DISPATCH_TIME_NOW),
|
||||
"es_delete_client not called within expected time window");
|
||||
|
||||
// Test move constructor
|
||||
{
|
||||
int fake;
|
||||
es_client_t *fakeClient = (es_client_t *)&fake;
|
||||
Client c1(fakeClient, ES_NEW_CLIENT_RESULT_SUCCESS);
|
||||
|
||||
Client c2(std::move(c1));
|
||||
|
||||
XCTAssertEqual(c1.Get(), nullptr);
|
||||
XCTAssertEqual(c2.Get(), fakeClient);
|
||||
XCTAssertEqual(c2.NewClientResult(), ES_NEW_CLIENT_RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
// Ensure `es_delete_client` was only called once when both `c1` and `c2`
|
||||
// are destructed.
|
||||
XCTAssertEqual(0, dispatch_semaphore_wait(gSema, DISPATCH_TIME_NOW),
|
||||
"es_delete_client not called within expected time window");
|
||||
XCTAssertNotEqual(0, dispatch_semaphore_wait(gSema, DISPATCH_TIME_NOW),
|
||||
"es_delete_client called unexpectedly");
|
||||
|
||||
// Test move assignment
|
||||
{
|
||||
int fake;
|
||||
es_client_t *fakeClient = (es_client_t *)&fake;
|
||||
Client c1(fakeClient, ES_NEW_CLIENT_RESULT_SUCCESS);
|
||||
Client c2;
|
||||
|
||||
c2 = std::move(c1);
|
||||
|
||||
XCTAssertEqual(c1.Get(), nullptr);
|
||||
XCTAssertEqual(c2.Get(), fakeClient);
|
||||
XCTAssertEqual(c2.NewClientResult(), ES_NEW_CLIENT_RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
// Ensure `es_delete_client` was only called once when both `c1` and `c2`
|
||||
// are destructed.
|
||||
XCTAssertEqual(0, dispatch_semaphore_wait(gSema, DISPATCH_TIME_NOW),
|
||||
"es_delete_client not called within expected time window");
|
||||
XCTAssertNotEqual(0, dispatch_semaphore_wait(gSema, DISPATCH_TIME_NOW),
|
||||
"es_delete_client called unexpectedly");
|
||||
}
|
||||
|
||||
- (void)testIsConnected {
|
||||
XCTAssertFalse(Client().IsConnected());
|
||||
XCTAssertFalse(Client(nullptr, ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED).IsConnected());
|
||||
XCTAssertTrue(Client(nullptr, ES_NEW_CLIENT_RESULT_SUCCESS).IsConnected());
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,58 @@
|
||||
/// 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__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_ENDPOINTSECURITYAPI_H
|
||||
#define SANTA__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_ENDPOINTSECURITYAPI_H
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
|
||||
class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurityAPI> {
|
||||
public:
|
||||
virtual ~EndpointSecurityAPI() = default;
|
||||
|
||||
virtual Client NewClient(void (^message_handler)(es_client_t *, Message));
|
||||
|
||||
virtual bool Subscribe(const Client &client, const std::set<es_event_type_t> &);
|
||||
|
||||
virtual es_message_t *RetainMessage(const es_message_t *msg);
|
||||
virtual void ReleaseMessage(es_message_t *msg);
|
||||
|
||||
virtual bool RespondAuthResult(const Client &client, const Message &msg, es_auth_result_t result,
|
||||
bool cache);
|
||||
|
||||
virtual bool MuteProcess(const Client &client, const audit_token_t *tok);
|
||||
|
||||
virtual bool ClearCache(const Client &client);
|
||||
|
||||
virtual uint32_t ExecArgCount(const es_event_exec_t *event);
|
||||
virtual es_string_token_t ExecArg(const es_event_exec_t *event, uint32_t index);
|
||||
|
||||
virtual uint32_t ExecEnvCount(const es_event_exec_t *event);
|
||||
virtual es_string_token_t ExecEnv(const es_event_exec_t *event, uint32_t index);
|
||||
|
||||
virtual uint32_t ExecFDCount(const es_event_exec_t *event);
|
||||
virtual const es_fd_t *ExecFD(const es_event_exec_t *event, uint32_t index);
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,111 @@
|
||||
/// 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/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
#include <EndpointSecurity/ESTypes.h>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
|
||||
Client EndpointSecurityAPI::NewClient(void (^message_handler)(es_client_t *, Message)) {
|
||||
es_client_t *client = NULL;
|
||||
|
||||
auto shared_esapi = shared_from_this();
|
||||
es_new_client_result_t res = es_new_client(&client, ^(es_client_t *c, const es_message_t *msg) {
|
||||
@autoreleasepool {
|
||||
message_handler(c, Message(shared_esapi, msg));
|
||||
}
|
||||
});
|
||||
|
||||
return Client(client, res);
|
||||
}
|
||||
|
||||
es_message_t *EndpointSecurityAPI::RetainMessage(const es_message_t *msg) {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
es_retain_message(msg);
|
||||
es_message_t *nonconst = const_cast<es_message_t *>(msg);
|
||||
return nonconst;
|
||||
} else {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
return es_copy_message(msg);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
|
||||
void EndpointSecurityAPI::ReleaseMessage(es_message_t *msg) {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
es_release_message(msg);
|
||||
} else {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
return es_free_message(msg);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::Subscribe(const Client &client,
|
||||
const std::set<es_event_type_t> &event_types) {
|
||||
std::vector<es_event_type_t> subs(event_types.begin(), event_types.end());
|
||||
return es_subscribe(client.Get(), subs.data(), (uint32_t)subs.size()) == ES_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::RespondAuthResult(const Client &client, const Message &msg,
|
||||
es_auth_result_t result, bool cache) {
|
||||
return es_respond_auth_result(client.Get(), &(*msg), result, cache) == ES_RESPOND_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::MuteProcess(const Client &client, const audit_token_t *tok) {
|
||||
return es_mute_process(client.Get(), tok) == ES_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::ClearCache(const Client &client) {
|
||||
return es_clear_cache(client.Get()) == ES_CLEAR_CACHE_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
uint32_t EndpointSecurityAPI::ExecArgCount(const es_event_exec_t *event) {
|
||||
return es_exec_arg_count(event);
|
||||
}
|
||||
|
||||
es_string_token_t EndpointSecurityAPI::ExecArg(const es_event_exec_t *event, uint32_t index) {
|
||||
return es_exec_arg(event, index);
|
||||
}
|
||||
|
||||
uint32_t EndpointSecurityAPI::ExecEnvCount(const es_event_exec_t *event) {
|
||||
return es_exec_env_count(event);
|
||||
}
|
||||
|
||||
es_string_token_t EndpointSecurityAPI::ExecEnv(const es_event_exec_t *event, uint32_t index) {
|
||||
return es_exec_env(event, index);
|
||||
}
|
||||
|
||||
uint32_t EndpointSecurityAPI::ExecFDCount(const es_event_exec_t *event) {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
return es_exec_fd_count(event);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const es_fd_t *EndpointSecurityAPI::ExecFD(const es_event_exec_t *event, uint32_t index) {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
return es_exec_fd(event, index);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
326
Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h
Normal file
@@ -0,0 +1,326 @@
|
||||
/// 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.
|
||||
|
||||
/// This file groups all of the enriched message types - that is the
|
||||
/// objects that are constructed to hold all enriched event data prior
|
||||
/// to being logged.
|
||||
|
||||
#ifndef SANTA__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_ENRICHEDTYPES_H
|
||||
#define SANTA__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_ENRICHEDTYPES_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
|
||||
class EnrichedFile {
|
||||
public:
|
||||
EnrichedFile()
|
||||
: user_(std::nullopt), group_(std::nullopt), hash_(std::nullopt) {}
|
||||
|
||||
EnrichedFile(std::optional<std::shared_ptr<std::string>> &&user,
|
||||
std::optional<std::shared_ptr<std::string>> &&group,
|
||||
std::optional<std::shared_ptr<std::string>> &&hash)
|
||||
: user_(std::move(user)),
|
||||
group_(std::move(group)),
|
||||
hash_(std::move(hash)) {}
|
||||
|
||||
EnrichedFile(EnrichedFile &&other)
|
||||
: user_(std::move(other.user_)),
|
||||
group_(std::move(other.group_)),
|
||||
hash_(std::move(other.hash_)) {}
|
||||
|
||||
EnrichedFile(const EnrichedFile &other) = delete;
|
||||
|
||||
const std::optional<std::shared_ptr<std::string>> &user() const {
|
||||
return user_;
|
||||
}
|
||||
const std::optional<std::shared_ptr<std::string>> &group() const {
|
||||
return group_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<std::shared_ptr<std::string>> user_;
|
||||
std::optional<std::shared_ptr<std::string>> group_;
|
||||
std::optional<std::shared_ptr<std::string>> hash_;
|
||||
};
|
||||
|
||||
class EnrichedProcess {
|
||||
public:
|
||||
EnrichedProcess()
|
||||
: effective_user_(std::nullopt),
|
||||
effective_group_(std::nullopt),
|
||||
real_user_(std::nullopt),
|
||||
real_group_(std::nullopt) {}
|
||||
|
||||
EnrichedProcess(std::optional<std::shared_ptr<std::string>> &&effective_user,
|
||||
std::optional<std::shared_ptr<std::string>> &&effective_group,
|
||||
std::optional<std::shared_ptr<std::string>> &&real_user,
|
||||
std::optional<std::shared_ptr<std::string>> &&real_group,
|
||||
EnrichedFile &&executable)
|
||||
: effective_user_(std::move(effective_user)),
|
||||
effective_group_(std::move(effective_group)),
|
||||
real_user_(std::move(real_user)),
|
||||
real_group_(std::move(real_group)),
|
||||
executable_(std::move(executable)) {}
|
||||
|
||||
EnrichedProcess(EnrichedProcess &&other)
|
||||
: effective_user_(std::move(other.effective_user_)),
|
||||
effective_group_(std::move(other.effective_group_)),
|
||||
real_user_(std::move(other.real_user_)),
|
||||
real_group_(std::move(other.real_group_)),
|
||||
executable_(std::move(other.executable_)) {}
|
||||
|
||||
EnrichedProcess(const EnrichedProcess &other) = delete;
|
||||
|
||||
const std::optional<std::shared_ptr<std::string>> &effective_user() const {
|
||||
return effective_user_;
|
||||
}
|
||||
const std::optional<std::shared_ptr<std::string>> &effective_group() const {
|
||||
return effective_group_;
|
||||
}
|
||||
const std::optional<std::shared_ptr<std::string>> &real_user() const {
|
||||
return real_user_;
|
||||
}
|
||||
const std::optional<std::shared_ptr<std::string>> &real_group() const {
|
||||
return real_group_;
|
||||
}
|
||||
const EnrichedFile &executable() const { return executable_; }
|
||||
|
||||
private:
|
||||
std::optional<std::shared_ptr<std::string>> effective_user_;
|
||||
std::optional<std::shared_ptr<std::string>> effective_group_;
|
||||
std::optional<std::shared_ptr<std::string>> real_user_;
|
||||
std::optional<std::shared_ptr<std::string>> real_group_;
|
||||
EnrichedFile executable_;
|
||||
};
|
||||
|
||||
class EnrichedEventType {
|
||||
public:
|
||||
EnrichedEventType(Message &&es_msg, EnrichedProcess &&instigator)
|
||||
: es_msg_(std::move(es_msg)), instigator_(std::move(instigator)) {
|
||||
clock_gettime(CLOCK_REALTIME, &enrichment_time_);
|
||||
}
|
||||
|
||||
EnrichedEventType(EnrichedEventType &&other)
|
||||
: es_msg_(std::move(other.es_msg_)),
|
||||
instigator_(std::move(other.instigator_)),
|
||||
enrichment_time_(std::move(other.enrichment_time_)) {}
|
||||
|
||||
EnrichedEventType(const EnrichedEventType &other) = delete;
|
||||
|
||||
virtual ~EnrichedEventType() = default;
|
||||
|
||||
const es_message_t &es_msg() const { return *es_msg_; }
|
||||
const EnrichedProcess &instigator() const { return instigator_; }
|
||||
struct timespec enrichment_time() const {
|
||||
// No reason to return a reference
|
||||
return enrichment_time_;
|
||||
}
|
||||
|
||||
private:
|
||||
Message es_msg_;
|
||||
EnrichedProcess instigator_;
|
||||
struct timespec enrichment_time_;
|
||||
};
|
||||
|
||||
class EnrichedClose : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedClose(Message &&es_msg, EnrichedProcess &&instigator,
|
||||
EnrichedFile &&target)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
target_(std::move(target)) {}
|
||||
|
||||
EnrichedClose(EnrichedClose &&other)
|
||||
: EnrichedEventType(std::move(other)),
|
||||
target_(std::move(other.target_)) {}
|
||||
|
||||
EnrichedClose(const EnrichedClose &other) = delete;
|
||||
|
||||
const EnrichedFile &target() const { return target_; }
|
||||
|
||||
private:
|
||||
EnrichedFile target_;
|
||||
};
|
||||
|
||||
class EnrichedExchange : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedExchange(Message &&es_msg, EnrichedProcess &&instigator,
|
||||
EnrichedFile &&file1, EnrichedFile &&file2)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
file1_(std::move(file1)),
|
||||
file2_(std::move(file2)) {}
|
||||
|
||||
EnrichedExchange(EnrichedExchange &&other)
|
||||
: EnrichedEventType(std::move(other)),
|
||||
file1_(std::move(other.file1_)),
|
||||
file2_(std::move(other.file2_)) {}
|
||||
|
||||
EnrichedExchange(const EnrichedExchange &other) = delete;
|
||||
|
||||
const EnrichedFile &file1() const { return file1_; }
|
||||
const EnrichedFile &file2() const { return file2_; }
|
||||
|
||||
private:
|
||||
EnrichedFile file1_;
|
||||
EnrichedFile file2_;
|
||||
};
|
||||
|
||||
class EnrichedExec : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedExec(Message &&es_msg, EnrichedProcess &&instigator,
|
||||
EnrichedProcess &&target, std::optional<EnrichedFile> &&script,
|
||||
std::optional<EnrichedFile> working_dir)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
target_(std::move(target)),
|
||||
script_(std::move(script)),
|
||||
working_dir_(std::move(working_dir)) {}
|
||||
|
||||
EnrichedExec(EnrichedExec &&other)
|
||||
: EnrichedEventType(std::move(other)),
|
||||
target_(std::move(other.target_)),
|
||||
script_(std::move(other.script_)),
|
||||
working_dir_(std::move(other.working_dir_)) {}
|
||||
|
||||
EnrichedExec(const EnrichedExec &other) = delete;
|
||||
|
||||
const EnrichedProcess &target() const { return target_; }
|
||||
const std::optional<EnrichedFile> &script() const { return script_; }
|
||||
const std::optional<EnrichedFile> &working_dir() const {
|
||||
return working_dir_;
|
||||
}
|
||||
|
||||
private:
|
||||
EnrichedProcess target_;
|
||||
std::optional<EnrichedFile> script_;
|
||||
std::optional<EnrichedFile> working_dir_;
|
||||
};
|
||||
|
||||
class EnrichedExit : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedExit(Message &&es_msg, EnrichedProcess &&instigator)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
|
||||
|
||||
EnrichedExit(EnrichedExit &&other) : EnrichedEventType(std::move(other)) {}
|
||||
|
||||
EnrichedExit(const EnrichedExit &other) = delete;
|
||||
};
|
||||
|
||||
class EnrichedFork : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedFork(Message &&es_msg, EnrichedProcess &&instigator,
|
||||
EnrichedProcess &&child)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
child_(std::move(child)) {}
|
||||
|
||||
EnrichedFork(EnrichedFork &&other)
|
||||
: EnrichedEventType(std::move(other)), child_(std::move(other.child_)) {}
|
||||
|
||||
EnrichedFork(const EnrichedFork &other) = delete;
|
||||
|
||||
const EnrichedProcess &child() const { return child_; }
|
||||
|
||||
private:
|
||||
EnrichedProcess child_;
|
||||
};
|
||||
|
||||
class EnrichedLink : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedLink(Message &&es_msg, EnrichedProcess &&instigator,
|
||||
EnrichedFile &&source, EnrichedFile &&target_dir)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
source_(std::move(source)),
|
||||
target_dir_(std::move(target_dir)) {}
|
||||
|
||||
EnrichedLink(EnrichedLink &&other)
|
||||
: EnrichedEventType(std::move(other)),
|
||||
source_(std::move(other.source_)),
|
||||
target_dir_(std::move(other.target_dir_)) {}
|
||||
|
||||
EnrichedLink(const EnrichedLink &other) = delete;
|
||||
|
||||
const EnrichedFile &source() const { return source_; }
|
||||
|
||||
private:
|
||||
EnrichedFile source_;
|
||||
EnrichedFile target_dir_;
|
||||
};
|
||||
|
||||
class EnrichedRename : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedRename(Message &&es_msg, EnrichedProcess &&instigator,
|
||||
EnrichedFile &&source, std::optional<EnrichedFile> &&target,
|
||||
std::optional<EnrichedFile> &&target_dir)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
source_(std::move(source)),
|
||||
target_(std::move(target)),
|
||||
target_dir_(std::move(target_dir)) {}
|
||||
|
||||
EnrichedRename(EnrichedRename &&other)
|
||||
: EnrichedEventType(std::move(other)),
|
||||
source_(std::move(other.source_)),
|
||||
target_(std::move(other.target_)),
|
||||
target_dir_(std::move(other.target_dir_)) {}
|
||||
|
||||
EnrichedRename(const EnrichedRename &other) = delete;
|
||||
|
||||
const EnrichedFile &source() const { return source_; }
|
||||
|
||||
private:
|
||||
EnrichedFile source_;
|
||||
std::optional<EnrichedFile> target_;
|
||||
std::optional<EnrichedFile> target_dir_;
|
||||
};
|
||||
|
||||
class EnrichedUnlink : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedUnlink(Message &&es_msg, EnrichedProcess &&instigator,
|
||||
EnrichedFile &&target)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
target_(std::move(target)) {}
|
||||
|
||||
EnrichedUnlink(EnrichedUnlink &&other)
|
||||
: EnrichedEventType(std::move(other)),
|
||||
target_(std::move(other.target_)) {}
|
||||
|
||||
EnrichedUnlink(const EnrichedUnlink &other) = delete;
|
||||
|
||||
const EnrichedFile &target() const { return target_; }
|
||||
|
||||
private:
|
||||
EnrichedFile target_;
|
||||
};
|
||||
|
||||
using EnrichedType =
|
||||
std::variant<EnrichedClose, EnrichedExchange, EnrichedExec, EnrichedExit,
|
||||
EnrichedFork, EnrichedLink, EnrichedRename, EnrichedUnlink>;
|
||||
|
||||
class EnrichedMessage {
|
||||
public:
|
||||
EnrichedMessage(EnrichedType &&msg) : msg_(std::move(msg)) {}
|
||||
|
||||
const EnrichedType &GetEnrichedMessage() { return msg_; }
|
||||
|
||||
private:
|
||||
EnrichedType msg_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
|
||||
#endif
|
||||