Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd23a5c3b7 | ||
|
|
ec203e8796 | ||
|
|
57ff69208d | ||
|
|
f00b7d2ded | ||
|
|
9791fdd53c | ||
|
|
26e2203f1e | ||
|
|
4a47195d12 | ||
|
|
4436e221df | ||
|
|
deccc8a148 | ||
|
|
06da796a4d | ||
|
|
7b99a76d0d | ||
|
|
c2d3e99446 | ||
|
|
6db7fea8ae | ||
|
|
6fcb4cfe63 | ||
|
|
8b55ee4da5 | ||
|
|
cc3177502c | ||
|
|
a49a59b109 | ||
|
|
2c06c39c82 | ||
|
|
234f81ea7c | ||
|
|
743c567bf8 | ||
|
|
21220f1499 | ||
|
|
39f3ffe8fc | ||
|
|
fdb01928a0 | ||
|
|
fbefbc5910 | ||
|
|
9db00d143d | ||
|
|
1cc40d59d8 | ||
|
|
ba1ace56f0 | ||
|
|
6d911e9d6e | ||
|
|
7e2b291122 | ||
|
|
64096f5d08 | ||
|
|
aec1c74fab | ||
|
|
d4a0d77cb9 | ||
|
|
7df209ed3f | ||
|
|
b7421e4499 | ||
|
|
e044fe3601 | ||
|
|
a67801d5ed | ||
|
|
3d37a3a5ae | ||
|
|
bfae5dc828 | ||
|
|
fde5f52a11 | ||
|
|
01bd1bfdca | ||
|
|
ae13900676 | ||
|
|
a65c91874b | ||
|
|
6a3fda069c | ||
|
|
4d34099142 | ||
|
|
e639574973 | ||
|
|
636f9ea873 | ||
|
|
9099409915 | ||
|
|
976f483a99 | ||
|
|
8a32b7a56b | ||
|
|
7eeb06b406 | ||
|
|
4540a1c656 | ||
|
|
acc7b32b24 | ||
|
|
b92d513f5d | ||
|
|
3458fccd4e | ||
|
|
fdfb00368c | ||
|
|
6bd369cfb2 | ||
|
|
0df26c6214 | ||
|
|
6e22da1d97 | ||
|
|
1725809335 | ||
|
|
3eff49feda | ||
|
|
5caedebb06 | ||
|
|
d823028b72 | ||
|
|
49b2d6e22a | ||
|
|
4236d57e96 | ||
|
|
36d463a1dc | ||
|
|
adbafd6bab | ||
|
|
b5ebe1259c | ||
|
|
e0ae0f481b | ||
|
|
8037c79fc0 | ||
|
|
892d303de1 | ||
|
|
ff3979263e | ||
|
|
01afefd3d4 | ||
|
|
830627e7bc | ||
|
|
601d726fcc | ||
|
|
0be1ca0199 | ||
|
|
8602593149 | ||
|
|
9bca601ce6 | ||
|
|
c73acd59d4 | ||
|
|
3c334e8882 | ||
|
|
5f811cadf8 | ||
|
|
4252475de0 | ||
|
|
45f1822681 | ||
|
|
498a23d907 | ||
|
|
5dff8a18f4 | ||
|
|
676c02626d | ||
|
|
64950d0a99 |
1
.bazelversion
Normal file
@@ -0,0 +1 @@
|
||||
5.0.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]+$'"
|
||||
58
.github/workflows/ci.yml
vendored
@@ -1,84 +1,50 @@
|
||||
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
|
||||
run: ./Testing/lint.sh
|
||||
|
||||
|
||||
build_userspace:
|
||||
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
|
||||
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
|
||||
@@ -87,13 +53,11 @@ jobs:
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ./CoverageData/info.lcov
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
33
BUILD
@@ -1,6 +1,5 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
load("//:version.bzl", "SANTA_VERSION")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
@@ -11,13 +10,14 @@ exports_files(["LICENSE"])
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_label_pattern = "{build}",
|
||||
build_version = SANTA_VERSION + ".{build}",
|
||||
build_label_pattern = ".*santa_{release}\\.{build}",
|
||||
build_version = "{release}.{build}",
|
||||
capture_groups = {
|
||||
"release": "\\d{4}\\.\\d+",
|
||||
"build": "\\d+",
|
||||
},
|
||||
fallback_build_label = "1",
|
||||
short_version_string = SANTA_VERSION,
|
||||
fallback_build_label = "santa_9999.1.1",
|
||||
short_version_string = "{release}",
|
||||
)
|
||||
|
||||
# Used to detect release builds
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -54,6 +55,7 @@ run_command(
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.syncservice.plist 2>/dev/null
|
||||
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
|
||||
""",
|
||||
)
|
||||
@@ -64,6 +66,7 @@ run_command(
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
""",
|
||||
)
|
||||
@@ -71,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
|
||||
@@ -93,11 +96,12 @@ 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",
|
||||
"Conf/com.google.santa.metricservice.plist",
|
||||
"Conf/com.google.santa.syncservice.plist",
|
||||
"Conf/com.google.santad.plist",
|
||||
"Conf/com.google.santa.plist",
|
||||
"Conf/com.google.santa.newsyslog.conf",
|
||||
@@ -107,7 +111,7 @@ genrule(
|
||||
"Conf/Package/postinstall",
|
||||
"Conf/Package/preinstall",
|
||||
],
|
||||
outs = ["santa-" + SANTA_VERSION + ".tar.gz"],
|
||||
outs = ["santa-release.tar.gz"],
|
||||
cmd = select({
|
||||
"//conditions:default": """
|
||||
echo "ERROR: Trying to create a release tarball without optimization."
|
||||
@@ -149,6 +153,10 @@ genrule(
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santametricservice.dSYM
|
||||
;;
|
||||
*santasyncservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santasyncservice.dSYM
|
||||
;;
|
||||
*Santa.app.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
|
||||
@@ -183,6 +191,7 @@ 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",
|
||||
|
||||
@@ -48,6 +48,7 @@ readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.s
|
||||
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
|
||||
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
|
||||
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
|
||||
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
|
||||
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
|
||||
|
||||
@@ -64,7 +65,7 @@ readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
|
||||
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
EN="${ENTITLEMENTS}/${BN}.entitlements"
|
||||
|
||||
@@ -113,11 +114,11 @@ echo "verifying signatures"
|
||||
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
|
||||
|
||||
echo "creating fresh release tarball"
|
||||
/bin/mkdir -p "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/binaries" "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/conf" "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/dsym" "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/usr/bin/tar -C "${RELEASE_ROOT}" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
|
||||
/bin/mkdir -p "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/binaries" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/conf" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/dsym" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/usr/bin/tar -C "${SCRATCH}/tar_root" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
|
||||
|
||||
echo "creating app pkg"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
|
||||
@@ -130,6 +131,7 @@ echo "creating app pkg"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.syncservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
|
||||
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -27,7 +27,10 @@ fi
|
||||
# Unload metric service
|
||||
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
# Unload sync service
|
||||
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
@@ -59,6 +62,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
|
||||
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
@@ -74,6 +78,9 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
# Load com.google.santa.metricservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
|
||||
# Load com.google.santa.syncservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# remove helper XPC services
|
||||
/bin/launchctl remove com.google.santa.bundleservice
|
||||
/bin/launchctl remove com.google.santa.metricservice
|
||||
/bin/launchctl remove com.google.santa.syncservice
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
@@ -28,6 +29,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
|
||||
/bin/rm -f /usr/local/bin/santactl # just a symlink
|
||||
|
||||
10
README.md
@@ -1,13 +1,13 @@
|
||||
# Santa [](https://github.com/google/santa/actions/workflows/ci.yml) [](https://coveralls.io/github/google/santa?branch=main)
|
||||
|
||||
<p align="center">
|
||||
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
<img src="https://raw.githubusercontent.com/google/santa/main/Source/santa/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.
|
||||
|
||||
|
||||
@@ -3,13 +3,12 @@ load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
proto_library(
|
||||
name = "log_proto",
|
||||
name = "santa_proto",
|
||||
srcs = ["santa.proto"],
|
||||
deps = [
|
||||
"@com_google_protobuf//:any_proto",
|
||||
@@ -18,16 +17,15 @@ proto_library(
|
||||
)
|
||||
|
||||
objc_proto_library(
|
||||
name = "log_objc_proto",
|
||||
name = "santa_objc_proto",
|
||||
copts = ["-fno-objc-arc"],
|
||||
non_arc_srcs = ["Santa.pbobjc.m"],
|
||||
protos = [":log_proto"],
|
||||
protos = [":santa_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
features = ["layering_check"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
@@ -48,15 +46,7 @@ objc_library(
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -70,6 +60,7 @@ objc_library(
|
||||
":SNTDeviceEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -83,6 +74,15 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTAllowlistInfo",
|
||||
srcs = ["SNTAllowlistInfo.m"],
|
||||
@@ -100,6 +100,7 @@ objc_library(
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTStrengthify",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
@@ -128,7 +129,6 @@ cc_library(
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -143,7 +143,6 @@ cc_library(
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = ["-std=c++11"],
|
||||
features = ["layering_check"],
|
||||
deps = [":SNTLogging"],
|
||||
)
|
||||
|
||||
@@ -151,7 +150,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(
|
||||
@@ -167,7 +175,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(
|
||||
@@ -200,10 +213,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",
|
||||
],
|
||||
)
|
||||
@@ -225,21 +245,12 @@ objc_library(
|
||||
deps = [":SNTCommonEnums"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncdInterface",
|
||||
srcs = ["SNTXPCSyncdInterface.m"],
|
||||
hdrs = ["SNTXPCSyncdInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncServiceInterface",
|
||||
srcs = ["SNTXPCSyncServiceInterface.m"],
|
||||
hdrs = ["SNTXPCSyncServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
|
||||
@@ -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%"
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
// response methods.
|
||||
typedef enum {
|
||||
ACTION_UNSET = 0,
|
||||
|
||||
@@ -74,15 +72,9 @@ typedef struct santa_vnode_id_t {
|
||||
bool operator==(const santa_vnode_id_t &rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
// This _must not_ be used for anything security-sensitive. It exists solely
|
||||
// to make the msleep/wakeup calls easier.
|
||||
uint64_t unsafe_simple_id() const {
|
||||
return (((uint64_t)fsid << 32) | fileid);
|
||||
}
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
// Message struct that is sent down the IODataQueue.
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
santa_vnode_id_t vnode_id;
|
||||
@@ -93,18 +85,17 @@ typedef struct {
|
||||
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];
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to a copy of
|
||||
// the message.
|
||||
// This points to a copy of the original ES message.
|
||||
void *es_message;
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to an
|
||||
// NSArray of the arguments.
|
||||
// This points to an NSArray of the process arguments.
|
||||
void *args_array;
|
||||
} santa_message_t;
|
||||
|
||||
|
||||
@@ -93,6 +93,22 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
// The return status of a sync.
|
||||
typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
|
||||
SNTSyncStatusTypeSuccess,
|
||||
SNTSyncStatusTypePreflightFailed,
|
||||
SNTSyncStatusTypeEventUploadFailed,
|
||||
SNTSyncStatusTypeRuleDownloadFailed,
|
||||
SNTSyncStatusTypePostflightFailed,
|
||||
SNTSyncStatusTypeTooManySyncsInProgress,
|
||||
SNTSyncStatusTypeMissingSyncBaseURL,
|
||||
SNTSyncStatusTypeMissingMachineID,
|
||||
SNTSyncStatusTypeDaemonTimeout,
|
||||
SNTSyncStatusTypeSyncStarted,
|
||||
SNTSyncStatusTypeUnknown,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
|
||||
@@ -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,32 @@
|
||||
///
|
||||
@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,9 +179,10 @@
|
||||
|
||||
///
|
||||
/// 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
|
||||
/// SNTEventLogTypeSyslog "syslog": Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog "file": Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// SNTEventLogTypeNull "null": Logs nothing
|
||||
/// SNTEventLogTypeProtobuf "protobuf": (BETA) Sent to a file on disk, using maildir format. Use
|
||||
/// mailDirectory to specify a path. Use mailDirectoryFileSizeThresholdKB,
|
||||
/// mailDirectorySizeThresholdMB and mailDirectoryEventMaxFlushTimeSec to configure
|
||||
/// additional maildir format settings.
|
||||
@@ -226,6 +255,16 @@
|
||||
|
||||
#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.
|
||||
@@ -411,6 +450,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.
|
||||
///
|
||||
|
||||
@@ -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,14 @@
|
||||
@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 +48,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 +66,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";
|
||||
@@ -111,6 +118,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 +155,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBModeKey : array,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
kSyncCleanRequired : number,
|
||||
kEnableAllEventUploadKey : number,
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
@@ -163,7 +173,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBModeKey : array,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kAboutText : string,
|
||||
kEnableSilentModeKey : string,
|
||||
kAboutTextKey : string,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
@@ -173,6 +184,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
@@ -207,10 +219,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 +293,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -286,6 +305,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSilentMode {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingAboutText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -394,6 +417,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingDisableUnknownEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -434,6 +465,26 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlockUSBMount {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBannedUSBBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBMode {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBBlockMessage {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUsbBlockMessage {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
@@ -541,6 +592,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 +617,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 +753,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;
|
||||
}
|
||||
@@ -734,6 +798,33 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return number ? [number boolValue] : YES;
|
||||
}
|
||||
|
||||
- (BOOL)enableCleanSyncEventUpload {
|
||||
NSNumber *number = self.configState[kSyncEnableCleanSyncEventUpload];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableAllEventUpload {
|
||||
NSNumber *n = self.syncState[kEnableAllEventUploadKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kEnableAllEventUploadKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setEnableAllEventUpload:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kEnableAllEventUploadKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (BOOL)disableUnknownEventUpload {
|
||||
NSNumber *n = self.syncState[kDisableUnknownEventUploadKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kDisableUnknownEventUploadKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setDisableUnknownEventUpload:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kDisableUnknownEventUploadKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (BOOL)enableForkAndExitLogging {
|
||||
NSNumber *number = self.configState[kEnableForkAndExitLogging];
|
||||
return number ? [number boolValue] : NO;
|
||||
@@ -879,7 +970,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 +1027,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
|
||||
|
||||
@@ -49,6 +49,9 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
/// Get the logging level for this process.
|
||||
LogLevel EffectiveLogLevel();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
@@ -32,6 +26,21 @@ void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
LogLevel EffectiveLogLevel() {
|
||||
#ifdef DEBUG
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if ([SNTConfigurator configurator].enableDebugLogging) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
});
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static NSString *binaryName;
|
||||
@@ -41,10 +50,6 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
dispatch_once(&pred, ^{
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
|
||||
if ([SNTConfigurator configurator].enableDebugLogging) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
|
||||
@@ -53,7 +58,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
}
|
||||
});
|
||||
|
||||
if (logLevel < level) return;
|
||||
if (EffectiveLogLevel() < level) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -27,9 +27,9 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
|
||||
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
|
||||
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
|
||||
default: typeStr = @"SNTMetricTypeUnknown"; break;
|
||||
default: typeStr = [NSString stringWithFormat:@"SNTMetricTypeUnknown %ld", metricType]; break;
|
||||
}
|
||||
return [NSString stringWithFormat:@"%@ %ld", typeStr, metricType];
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,6 +485,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;
|
||||
|
||||
@@ -45,10 +45,10 @@
|
||||
XCTAssertNotNil(c, @"Expected returned SNTMetricCounter to not be nil");
|
||||
[c incrementForFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(1, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremendted by 1");
|
||||
@"Counter not incremented by 1");
|
||||
[c incrementBy:3 forFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(4, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremendted by 3");
|
||||
@"Counter not incremented by 3");
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
@@ -630,39 +630,39 @@
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
|
||||
@"expected" : @"SNTMetricTypeConstantBool 1"
|
||||
@"expected" : @"SNTMetricTypeConstantBool"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
|
||||
@"expected" : @"SNTMetricTypeConstantString 2"
|
||||
@"expected" : @"SNTMetricTypeConstantString"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
|
||||
@"expected" : @"SNTMetricTypeConstantInt64 3"
|
||||
@"expected" : @"SNTMetricTypeConstantInt64"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
|
||||
@"expected" : @"SNTMetricTypeConstantDouble 4"
|
||||
@"expected" : @"SNTMetricTypeConstantDouble"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
|
||||
@"expected" : @"SNTMetricTypeGaugeBool 5"
|
||||
@"expected" : @"SNTMetricTypeGaugeBool"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
|
||||
@"expected" : @"SNTMetricTypeGaugeString 6"
|
||||
@"expected" : @"SNTMetricTypeGaugeString"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
|
||||
@"expected" : @"SNTMetricTypeGaugeInt64 7"
|
||||
@"expected" : @"SNTMetricTypeGaugeInt64"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
|
||||
@"expected" : @"SNTMetricTypeGaugeDouble 8"
|
||||
@"expected" : @"SNTMetricTypeGaugeDouble"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
|
||||
@"expected" : @"SNTMetricTypeCounter 9"
|
||||
@"expected" : @"SNTMetricTypeCounter"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -64,6 +64,11 @@
|
||||
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
|
||||
@@ -95,6 +95,12 @@
|
||||
///
|
||||
@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");
|
||||
|
||||
@@ -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;
|
||||
@@ -62,10 +64,12 @@ extern NSString *const kDecisionAllowUnknown;
|
||||
extern NSString *const kDecisionAllowBinary;
|
||||
extern NSString *const kDecisionAllowCertificate;
|
||||
extern NSString *const kDecisionAllowScope;
|
||||
extern NSString *const kDecisionAllowTeamID;
|
||||
extern NSString *const kDecisionBlockUnknown;
|
||||
extern NSString *const kDecisionBlockBinary;
|
||||
extern NSString *const kDecisionBlockCertificate;
|
||||
extern NSString *const kDecisionBlockScope;
|
||||
extern NSString *const kDecisionBlockTeamID;
|
||||
extern NSString *const kDecisionUnknown;
|
||||
extern NSString *const kDecisionBundleBinary;
|
||||
extern NSString *const kLoggedInUsers;
|
||||
@@ -89,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;
|
||||
@@ -132,5 +137,5 @@ extern const NSUInteger kDefaultEventBatchSize;
|
||||
/// Are represented in seconds
|
||||
///
|
||||
extern const NSUInteger kDefaultFullSyncInterval;
|
||||
extern const NSUInteger kDefaultFCMFullSyncInterval;
|
||||
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;
|
||||
extern const NSUInteger kDefaultPushNotificationsFullSyncInterval;
|
||||
extern const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline;
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "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";
|
||||
@@ -63,10 +65,12 @@ NSString *const kDecisionAllowUnknown = @"ALLOW_UNKNOWN";
|
||||
NSString *const kDecisionAllowBinary = @"ALLOW_BINARY";
|
||||
NSString *const kDecisionAllowCertificate = @"ALLOW_CERTIFICATE";
|
||||
NSString *const kDecisionAllowScope = @"ALLOW_SCOPE";
|
||||
NSString *const kDecisionAllowTeamID = @"ALLOW_TEAMID";
|
||||
NSString *const kDecisionBlockUnknown = @"BLOCK_UNKNOWN";
|
||||
NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
|
||||
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
|
||||
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
|
||||
NSString *const kDecisionBlockTeamID = @"BLOCK_TEAMID";
|
||||
NSString *const kDecisionUnknown = @"UNKNOWN";
|
||||
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
|
||||
NSString *const kLoggedInUsers = @"logged_in_users";
|
||||
@@ -90,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";
|
||||
@@ -125,5 +130,5 @@ NSString *const kLogSync = @"log_sync";
|
||||
|
||||
const NSUInteger kDefaultEventBatchSize = 50;
|
||||
const NSUInteger kDefaultFullSyncInterval = 600;
|
||||
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
|
||||
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;
|
||||
const NSUInteger kDefaultPushNotificationsFullSyncInterval = 14400;
|
||||
const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline = 600;
|
||||
@@ -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,11 +50,12 @@
|
||||
- (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
|
||||
///
|
||||
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -20,18 +20,37 @@
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// A block that reports the number of rules processed.
|
||||
/// TODO(bur): Add more details about the sync.
|
||||
typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
|
||||
|
||||
///
|
||||
/// Protocol implemented by syncservice and utilized by daemon and ctl for communication with a
|
||||
/// sync server.
|
||||
///
|
||||
@protocol SNTSyncServiceXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
- (void)performFullSyncWithReply:(SNTFullSyncReplyBlock)reply;
|
||||
|
||||
// The syncservice regularly syncs with a configured sync server. Use this method to sync out of
|
||||
// band. The syncservice ensures syncs do not run concurrently.
|
||||
//
|
||||
// Pass an NSXPCListenerEndpoint whose associated NSXPCListener exports an object that implements
|
||||
// the SNTSyncServiceLogReceiverXPC protocol. The caller will receive sync logs over this listener.
|
||||
// This is required.
|
||||
//
|
||||
// Syncs are enqueued in order and executed serially. kMaxEnqueuedSyncs limits the number of syncs
|
||||
// in the queue. If the queue is full calls to this method will be dropped and
|
||||
// SNTSyncStatusTypeTooManySyncsInProgress will be passed into the reply block.
|
||||
//
|
||||
// Pass true to isClean to perform a clean sync, defaults to false.
|
||||
//
|
||||
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
|
||||
isClean:(BOOL)cleanSync
|
||||
reply:(void (^)(SNTSyncStatusType))reply;
|
||||
|
||||
// Spindown the syncservice. The syncservice will not automatically start back up.
|
||||
// A new connection to the syncservice will bring it back up. This allows us to avoid running
|
||||
// the syncservice needlessly when there is no configured sync server.
|
||||
- (void)spindown;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncServiceInterface : NSObject
|
||||
@@ -54,3 +73,11 @@ typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
///
|
||||
/// Protocol implemented by santactl sync and used to receive log messages from
|
||||
/// the syncservice during a user initiated sync.
|
||||
///
|
||||
@protocol SNTSyncServiceLogReceiverXPC
|
||||
- (void)didReceiveLog:(NSString *)log;
|
||||
@end
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by santactl and utilized by santad
|
||||
@protocol SNTSyncdXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncdInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)syncdInterface;
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCSyncdInterface
|
||||
|
||||
+ (NSXPCInterface *)syncdInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,12 +28,11 @@
|
||||
@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 +40,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 +64,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;
|
||||
|
||||
@@ -79,6 +79,7 @@ message Execution {
|
||||
optional string original_path = 11;
|
||||
repeated string args = 12;
|
||||
optional string machine_id = 13;
|
||||
optional string team_id = 14;
|
||||
}
|
||||
|
||||
message DiskAppeared {
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
@@ -54,6 +68,7 @@ macos_application(
|
||||
"//Source/santactl": "MacOS",
|
||||
"//Source/santabundleservice": "MacOS",
|
||||
"//Source/santametricservice": "MacOS",
|
||||
"//Source/santasyncservice": "MacOS",
|
||||
"//Source/santad:com.google.santa.daemon": "Library/SystemExtensions",
|
||||
},
|
||||
app_icons = glob(["Resources/Images.xcassets/**"]),
|
||||
@@ -64,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.9",
|
||||
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"],
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,7 +17,7 @@
|
||||
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -15,7 +15,7 @@
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="515" height="318"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="515" height="318"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
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 |
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -21,13 +21,13 @@
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<view key="contentView" misplaced="YES" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="540" height="479"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="540" height="462"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
|
||||
<rect key="frame" x="16" y="451" width="37" height="32"/>
|
||||
<rect key="frame" x="16" y="434" width="37" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
@@ -240,17 +240,16 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="153" y="35" width="114" height="23"/>
|
||||
<rect key="frame" x="147" y="30" width="126" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<buttonCell key="cell" type="push" title="Open Event..." bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
|
||||
@@ -285,18 +284,17 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="277" y="33" width="112" height="25"/>
|
||||
<rect key="frame" x="271" y="28" width="124" height="34"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Ignore" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<buttonCell key="cell" type="push" title="Ignore" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
Gw
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
|
||||
</buttonCell>
|
||||
<accessibility description="Dismiss Dialog"/>
|
||||
<connections>
|
||||
@@ -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.
|
||||
@@ -156,6 +204,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
// Otherwise abandon bundle hashing and display the blockable event.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
[withController updateBlockNotification:event withBundleHash:nil];
|
||||
LOGE(@"Timeout connecting to bundle service");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,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;
|
||||
@@ -244,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;
|
||||
76
Source/gui/SNTNotificationManagerTest.m
Normal file
@@ -0,0 +1,76 @@
|
||||
/// 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 <MOLCertificate/MOLCertificate.h>
|
||||
// #import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#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",
|
||||
@@ -23,8 +28,17 @@ objc_library(
|
||||
macos_command_line_application(
|
||||
name = "santabundleservice",
|
||||
bundle_id = "com.google.santa.bundleservice",
|
||||
codesignopts = [
|
||||
"--timestamp",
|
||||
"--force",
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.9",
|
||||
minimum_os_version = "10.15",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":santabs_lib"],
|
||||
|
||||
@@ -5,7 +5,6 @@ licenses(["notice"])
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -49,9 +48,11 @@ objc_library(
|
||||
"//Source/common:SNTSystemInfo",
|
||||
"//Source/common:SNTXPCBundleServiceInterface",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCSyncServiceInterface",
|
||||
"//Source/common:SNTXPCUnprivilegedControlInterface",
|
||||
"//Source/santasyncservice:sync_lib",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -66,10 +67,10 @@ macos_command_line_application(
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.9",
|
||||
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"],
|
||||
@@ -89,6 +90,7 @@ santa_unit_test(
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
@@ -99,12 +101,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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -19,4 +19,5 @@
|
||||
|
||||
@interface SNTCommandMetrics : SNTCommand <SNTCommandProtocol>
|
||||
- (void)prettyPrintMetrics:(NSDictionary *)metircs asJSON:(BOOL)exportJSON;
|
||||
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args;
|
||||
@end
|
||||
|
||||
@@ -41,6 +41,7 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"Provides metrics about Santa's operation while it's running.\n"
|
||||
@"Pass prefixes to filter list of metrics, if desired.\n"
|
||||
@" Use --json to output in JSON format");
|
||||
}
|
||||
|
||||
@@ -118,10 +119,30 @@ 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"]];
|
||||
}
|
||||
|
||||
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args {
|
||||
NSMutableDictionary *outer = [metrics mutableCopy];
|
||||
NSMutableDictionary *inner = [NSMutableDictionary dictionary];
|
||||
__block BOOL hadFilter = NO;
|
||||
|
||||
[metrics[@"metrics"] enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
||||
for (NSString *arg in args) {
|
||||
if ([arg hasPrefix:@"-"]) continue;
|
||||
|
||||
hadFilter = YES;
|
||||
if ([key hasPrefix:arg]) {
|
||||
inner[key] = value;
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
outer[@"metrics"] = inner;
|
||||
return hadFilter ? outer : metrics;
|
||||
}
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
__block NSDictionary *metrics;
|
||||
|
||||
@@ -138,6 +159,8 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
|
||||
}
|
||||
|
||||
metrics = [self filterMetrics:metrics withArguments:arguments];
|
||||
|
||||
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -131,4 +131,26 @@
|
||||
@"Metrics command command did not produce expected output");
|
||||
}
|
||||
|
||||
- (void)testFiltering {
|
||||
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
|
||||
|
||||
NSDictionary *metricDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
|
||||
NSDictionary *filtered;
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[]];
|
||||
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"], @"No filtering with no args");
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json" ]];
|
||||
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"],
|
||||
@"No filtering with no metric args");
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json", @"/santa" ]];
|
||||
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 3,
|
||||
@"Expected filter of metrics with /santa to return 3 metrics");
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"/build", @"/santa" ]];
|
||||
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 4,
|
||||
@"Expected filter of metrics with /build and /santa to return 4 metrics");
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -82,7 +76,7 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
|
||||
BOOL cachingEnabled = [configurator enableSysxCache];
|
||||
|
||||
// Kext status
|
||||
// Cache status
|
||||
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
|
||||
if (cachingEnabled) {
|
||||
dispatch_group_enter(group);
|
||||
@@ -113,6 +107,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 +174,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 +202,9 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
@"transitive_rules" : @(transitiveRuleCount),
|
||||
@"events_pending_upload" : @(eventCount),
|
||||
},
|
||||
@"static_rules" : @{
|
||||
@"rule_count" : @(staticRuleCount),
|
||||
},
|
||||
@"sync" : @{
|
||||
@"server" : syncURLStr ?: @"null",
|
||||
@"clean_required" : @(syncCleanReqd),
|
||||
@@ -223,20 +228,20 @@ 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(">>> Database Info\n");
|
||||
@@ -247,6 +252,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]);
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDropRootPrivs.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/common/SNTXPCSyncServiceInterface.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
#import "Source/santasyncservice/SNTSyncManager.h"
|
||||
|
||||
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol>
|
||||
@property MOLXPCConnection *listener;
|
||||
@property SNTSyncManager *syncManager;
|
||||
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol, SNTSyncServiceLogReceiverXPC>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandSync
|
||||
@@ -38,9 +35,8 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Connect to santad while we are root, so that we pass the XPC authentication.
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return YES;
|
||||
return NO; // We talk directly with the syncservice.
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
@@ -66,53 +62,38 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
LOGE(@"Missing SyncBaseURL. Exiting.");
|
||||
exit(1);
|
||||
}
|
||||
MOLXPCConnection *ss = [SNTXPCSyncServiceInterface configuredConnection];
|
||||
ss.invalidationHandler = ^(void) {
|
||||
LOGE(@"Failed to connect to the sync service.");
|
||||
exit(1);
|
||||
};
|
||||
[ss resume];
|
||||
|
||||
BOOL daemon = [arguments containsObject:@"--daemon"];
|
||||
self.syncManager = [[SNTSyncManager alloc] initWithDaemonConnection:self.daemonConn
|
||||
isDaemon:daemon];
|
||||
NSXPCListener *logListener = [NSXPCListener anonymousListener];
|
||||
MOLXPCConnection *lr = [[MOLXPCConnection alloc] initServerWithListener:logListener];
|
||||
lr.exportedObject = self;
|
||||
lr.unprivilegedInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceLogReceiverXPC)];
|
||||
[lr resume];
|
||||
BOOL isClean = [NSProcessInfo.processInfo.arguments containsObject:@"--clean"];
|
||||
[[ss remoteObjectProxy]
|
||||
syncWithLogListener:logListener.endpoint
|
||||
isClean:isClean
|
||||
reply:^(SNTSyncStatusType status) {
|
||||
if (status == SNTSyncStatusTypeTooManySyncsInProgress) {
|
||||
[self didReceiveLog:@"Too many syncs in progress, try again later."];
|
||||
}
|
||||
exit((int)status);
|
||||
}];
|
||||
|
||||
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
|
||||
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
|
||||
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0
|
||||
diskCapacity:0
|
||||
diskPath:nil]];
|
||||
|
||||
if (!self.syncManager.daemon) return [self.syncManager fullSync];
|
||||
[self syncdWithDaemonConnection:self.daemonConn];
|
||||
// Do not return from this scope.
|
||||
[[NSRunLoop mainRunLoop] run];
|
||||
}
|
||||
|
||||
#pragma mark daemon methods
|
||||
|
||||
- (void)syncdWithDaemonConnection:(MOLXPCConnection *)daemonConn {
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
// Create listener for return connection from daemon.
|
||||
NSXPCListener *listener = [NSXPCListener anonymousListener];
|
||||
self.listener = [[MOLXPCConnection alloc] initServerWithListener:listener];
|
||||
self.listener.privilegedInterface = [SNTXPCSyncdInterface syncdInterface];
|
||||
self.listener.exportedObject = self.syncManager;
|
||||
self.listener.acceptedHandler = ^{
|
||||
LOGD(@"santad <--> santactl connections established");
|
||||
dispatch_semaphore_signal(sema);
|
||||
};
|
||||
self.listener.invalidationHandler = ^{
|
||||
// If santad is unloaded kill santactl
|
||||
LOGD(@"exiting");
|
||||
exit(0);
|
||||
};
|
||||
[self.listener resume];
|
||||
|
||||
// Tell daemon to connect back to the above listener.
|
||||
[[daemonConn remoteObjectProxy] setSyncdListener:listener.endpoint];
|
||||
|
||||
// Now wait for the connection to come in.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
self.listener.invalidationHandler = nil;
|
||||
[self.listener invalidate];
|
||||
[self performSelectorInBackground:@selector(syncdWithDaemonConnection:) withObject:daemonConn];
|
||||
}
|
||||
|
||||
[self.syncManager fullSyncSecondsFromNow:15];
|
||||
/// Implement the SNTSyncServiceLogReceiverXPC protocol.
|
||||
- (void)didReceiveLog:(NSString *)log {
|
||||
printf("%s\n", log.UTF8String);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
hostname | testHost
|
||||
username | testUser
|
||||
|
||||
>>> Metrics
|
||||
>>> Metrics
|
||||
Metric Name | /santa/rules
|
||||
Description | Number of rules
|
||||
Type | SNTMetricTypeGaugeInt64 7
|
||||
Type | SNTMetricTypeGaugeInt64
|
||||
Field | rule_type=binary
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
@@ -22,14 +22,14 @@
|
||||
|
||||
Metric Name | /proc/memory/resident_size
|
||||
Description | The resident set size of this process
|
||||
Type | SNTMetricTypeGaugeInt64 7
|
||||
Type | SNTMetricTypeGaugeInt64
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 123456789
|
||||
|
||||
Metric Name | /santa/events
|
||||
Description | Count of process exec events on the host
|
||||
Type | SNTMetricTypeCounter 9
|
||||
Type | SNTMetricTypeCounter
|
||||
Field | rule_type=binary
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
@@ -41,28 +41,28 @@
|
||||
|
||||
Metric Name | /santa/using_endpoint_security_framework
|
||||
Description | Is santad using the endpoint security framework
|
||||
Type | SNTMetricTypeConstantBool 1
|
||||
Type | SNTMetricTypeConstantBool
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 1
|
||||
|
||||
Metric Name | /proc/birth_timestamp
|
||||
Description | Start time of this santad instance, in microseconds since epoch
|
||||
Type | SNTMetricTypeConstantInt64 3
|
||||
Type | SNTMetricTypeConstantInt64
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 1250999830800
|
||||
|
||||
Metric Name | /proc/memory/virtual_size
|
||||
Description | The virtual memory size of this process
|
||||
Type | SNTMetricTypeGaugeInt64 7
|
||||
Type | SNTMetricTypeGaugeInt64
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 987654321
|
||||
|
||||
Metric Name | /build/label
|
||||
Description | Software version running
|
||||
Type | SNTMetricTypeConstantString 2
|
||||
Type | SNTMetricTypeConstantString
|
||||
Created | 2021-09-16T21:07:34.826Z
|
||||
Last Updated | 2021-09-16T21:07:34.826Z
|
||||
Data | 20210809.0.1
|
||||
|
||||
@@ -3,7 +3,6 @@ load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
@@ -22,11 +21,14 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
@@ -52,6 +54,8 @@ objc_library(
|
||||
deps = [
|
||||
":SNTEventProvider",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
@@ -69,15 +73,20 @@ objc_library(
|
||||
"Logs/SNTSyslogEventLog.h",
|
||||
"SNTDatabaseController.h",
|
||||
],
|
||||
hdrs = [
|
||||
"Logs/SNTEventLog.h",
|
||||
],
|
||||
deps = [
|
||||
":database_controller",
|
||||
"//Source/common:SNTAllowlistInfo",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"@FMDB",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -92,8 +101,14 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
":event_logs_common",
|
||||
"//Source/common:SNTAllowlistInfo",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:log_objc_proto",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:santa_objc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -108,6 +123,12 @@ objc_library(
|
||||
deps = [
|
||||
":endpoint_security_manager",
|
||||
":event_logs_common",
|
||||
"//Source/common:SNTAllowlistInfo",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -116,18 +137,25 @@ objc_library(
|
||||
srcs = [
|
||||
"Logs/SNTFileEventLog.h",
|
||||
"Logs/SNTFileEventLog.m",
|
||||
"Logs/SNTSyslogEventLog.h",
|
||||
],
|
||||
deps = [
|
||||
":event_logs_common",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStrengthify",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "event_logs",
|
||||
hdrs = ["Logs/SNTEventLog.h"],
|
||||
deps = [
|
||||
":file_event_logs",
|
||||
":protobuf_event_logs",
|
||||
":syslog_event_logs",
|
||||
"//Source/common:SNTCommon",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -169,24 +197,34 @@ objc_library(
|
||||
"IOKit",
|
||||
],
|
||||
deps = [
|
||||
":SNTApplicationCoreMetrics",
|
||||
":database_controller",
|
||||
":endpoint_security_manager",
|
||||
":event_logs",
|
||||
"//Source/common:SNTAllowlistInfo",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTStrengthify",
|
||||
"//Source/common:SNTXPCBundleServiceInterface",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCMetricServiceInterface",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"//Source/common:SNTXPCSyncdInterface",
|
||||
"//Source/santad:SNTApplicationCoreMetrics",
|
||||
"//Source/common:SNTXPCSyncServiceInterface",
|
||||
"//Source/common:SNTXPCUnprivilegedControlInterface",
|
||||
"//Source/common:SantaCache",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
@@ -210,6 +248,9 @@ objc_library(
|
||||
"EventProviders/EndpointSecurityTestUtil.h",
|
||||
"EventProviders/EndpointSecurityTestUtil.mm",
|
||||
],
|
||||
hdrs = [
|
||||
"EventProviders/EndpointSecurityTestUtil.h",
|
||||
],
|
||||
sdk_dylibs = [
|
||||
"EndpointSecurity",
|
||||
"bsm",
|
||||
@@ -246,12 +287,17 @@ macos_bundle(
|
||||
"--force",
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
entitlements = select({
|
||||
"//:adhoc_build": "com.google.santa.daemon.systemextension-adhoc.entitlements",
|
||||
# Non-adhoc builds get their entitlements from the provisioning profile.
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
infoplists = ["Info.plist"],
|
||||
linkopts = ["-execute"],
|
||||
minimum_os_version = "10.9",
|
||||
minimum_os_version = "10.15",
|
||||
provisioning_profile = select({
|
||||
"//:ci_build": None,
|
||||
"//conditions:default": "Santa_Daemon_Dev.provisionprofile",
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:daemon_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
@@ -261,6 +307,11 @@ macos_bundle(
|
||||
santa_unit_test(
|
||||
name = "SNTExecutionControllerTest",
|
||||
srcs = [
|
||||
"DataLayer/SNTDatabaseTable.h",
|
||||
"DataLayer/SNTEventTable.h",
|
||||
"DataLayer/SNTRuleTable.h",
|
||||
"EventProviders/SNTEventProvider.h",
|
||||
"SNTExecutionController.h",
|
||||
"SNTExecutionControllerTest.m",
|
||||
],
|
||||
sdk_dylibs = [
|
||||
@@ -273,6 +324,7 @@ santa_unit_test(
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -280,8 +332,9 @@ santa_unit_test(
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"//Source/common:SNTXPCSyncdInterface",
|
||||
"//Source/common:SantaCache",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
"@OCMock",
|
||||
@@ -316,8 +369,12 @@ santa_unit_test(
|
||||
"DataLayer/SNTRuleTable.m",
|
||||
"DataLayer/SNTRuleTableTest.m",
|
||||
],
|
||||
sdk_dylibs = [
|
||||
"EndpointSecurity",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -344,6 +401,8 @@ santa_unit_test(
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
@@ -352,6 +411,8 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTDeviceManagerTest",
|
||||
srcs = [
|
||||
"EventProviders/DiskArbitrationTestUtil.h",
|
||||
"EventProviders/SNTDeviceManager.h",
|
||||
"EventProviders/SNTDeviceManagerTest.mm",
|
||||
],
|
||||
minimum_os_version = "10.15",
|
||||
@@ -364,6 +425,8 @@ santa_unit_test(
|
||||
":EndpointSecurityTestLib",
|
||||
":santad_lib",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
"@OCMock",
|
||||
@@ -373,7 +436,9 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTApplicationTest",
|
||||
srcs = [
|
||||
"SNTApplication.h",
|
||||
"SNTApplicationTest.m",
|
||||
"SNTDatabaseController.h",
|
||||
],
|
||||
data = [
|
||||
"//Source/santad/testdata:binaryrules_testdata",
|
||||
@@ -383,9 +448,13 @@ santa_unit_test(
|
||||
"EndpointSecurity",
|
||||
"bsm",
|
||||
],
|
||||
tags = ["exclusive"],
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
":santad_lib",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
"@OCMock",
|
||||
@@ -423,6 +492,7 @@ santa_unit_test(
|
||||
deps = [
|
||||
":SNTApplicationCoreMetrics",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTSystemInfo",
|
||||
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
|
||||
@@ -433,14 +503,22 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTProtobufEventLogTest",
|
||||
srcs = [
|
||||
"Logs/SNTLogOutput.h",
|
||||
"Logs/SNTProtobufEventLog.h",
|
||||
"Logs/SNTProtobufEventLogTest.m",
|
||||
"Logs/SNTSimpleMaildir.h",
|
||||
],
|
||||
minimum_os_version = "10.15",
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
":event_logs",
|
||||
"//Source/common:SNTAllowlistInfo",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:log_objc_proto",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:santa_objc_proto",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
@@ -29,6 +30,45 @@ static const NSUInteger kTransitiveRuleCullingThreshold = 500000;
|
||||
// Consider transitive rules out of date if they haven't been used in six months.
|
||||
static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
|
||||
|
||||
static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABLE(macos(12.0)) {
|
||||
// Note: This function uses API introduced in macOS 12, but we want to continue to support
|
||||
// building in older environments. API Availability checks do not help for this use case,
|
||||
// instead we use the following preprocessor macros to conditionally compile these API. The
|
||||
// drawback here is that if a pre-macOS 12 SDK is used to build Santa and it is then deployed
|
||||
// on macOS 12 or later, the dynamic mute set will not be computed.
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
// Create a temporary ES client in order to grab the default set of muted paths.
|
||||
// TODO(mlw): Reorganize this code so that a temporary ES client doesn't need to be created
|
||||
es_client_t *client = NULL;
|
||||
es_new_client_result_t ret = es_new_client(&client, ^(es_client_t *c, const es_message_t *m){
|
||||
// noop
|
||||
});
|
||||
|
||||
if (ret != ES_NEW_CLIENT_RESULT_SUCCESS) {
|
||||
// Creating the client failed, so we cannot grab the current default mute set.
|
||||
LOGE(@"Failed to create client to grab default muted paths");
|
||||
return;
|
||||
}
|
||||
|
||||
es_muted_paths_t *mps = NULL;
|
||||
if (es_muted_paths_events(client, &mps) != ES_RETURN_SUCCESS) {
|
||||
LOGE(@"Failed to obtain list of default muted paths.");
|
||||
es_delete_client(client);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mps->count; i++) {
|
||||
// Only add literal paths, prefix paths would require recursive directory search
|
||||
if (mps->paths[i].type == ES_MUTE_PATH_TYPE_LITERAL) {
|
||||
[criticalPaths addObject:@(mps->paths[i].path.data)];
|
||||
}
|
||||
}
|
||||
|
||||
es_release_muted_paths(mps);
|
||||
es_delete_client(client);
|
||||
#endif
|
||||
}
|
||||
|
||||
@interface SNTRuleTable ()
|
||||
@property MOLCodesignChecker *santadCSInfo;
|
||||
@property MOLCodesignChecker *launchdCSInfo;
|
||||
@@ -42,32 +82,54 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
|
||||
// ES on Monterey now has a “default mute set” of paths that are automatically applied to each ES
|
||||
// client. This mute set contains most (not all) AUTH event types for some paths that were deemed
|
||||
// “system critical”.
|
||||
// Retain this list for < 12.0 versions of ES, but we should be able to rely on the paths muted by
|
||||
// default (visible with es_muted_paths_events any time after connecting a new client and before
|
||||
// modifying any of the mute state).
|
||||
+ (NSArray *)criticalSystemBinaryPaths {
|
||||
return @[
|
||||
@"/usr/libexec/trustd",
|
||||
@"/usr/libexec/xpcproxy",
|
||||
@"/usr/libexec/amfid",
|
||||
@"/usr/libexec/opendirectoryd",
|
||||
@"/usr/libexec/runningboardd",
|
||||
@"/usr/libexec/syspolicyd",
|
||||
@"/usr/libexec/watchdogd",
|
||||
@"/usr/libexec/cfprefsd",
|
||||
@"/usr/sbin/securityd",
|
||||
@"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/Resources/tccd",
|
||||
@"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer",
|
||||
@"/usr/sbin/ocspd",
|
||||
@"/usr/lib/dyld",
|
||||
@"/Applications/Santa.app/Contents/MacOS/Santa",
|
||||
@"/Applications/Santa.app/Contents/MacOS/santactl",
|
||||
@"/Applications/Santa.app/Contents/MacOS/santabundleservice",
|
||||
// This entry is for on <10.15 - on 10.15+ the binary is actually executed
|
||||
// from a system-controlled path but will only ever be executed by
|
||||
// the OS anyway.
|
||||
@"/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon",
|
||||
];
|
||||
static dispatch_once_t onceToken;
|
||||
static NSArray *criticalPaths = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
// These paths have previously existed in the ES default mute set. They are hardcoded
|
||||
// here in case grabbing the current default mute set fails, or if Santa is running on
|
||||
// an OS that did not yet support this feature.
|
||||
NSSet *fallbackDefaultMuteSet = [[NSSet alloc] initWithArray:@[
|
||||
@"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer",
|
||||
@"/System/Library/PrivateFrameworks/TCC.framework/Support/tccd",
|
||||
@"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/Resources/tccd",
|
||||
@"/usr/sbin/cfprefsd",
|
||||
@"/usr/sbin/securityd",
|
||||
@"/usr/libexec/opendirectoryd",
|
||||
@"/usr/libexec/sandboxd",
|
||||
@"/usr/libexec/syspolicyd",
|
||||
@"/usr/libexec/runningboardd",
|
||||
@"/usr/libexec/amfid",
|
||||
@"/usr/libexec/watchdogd",
|
||||
]];
|
||||
|
||||
// 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",
|
||||
@"/usr/libexec/xpcproxy",
|
||||
@"/usr/sbin/ocspd",
|
||||
@"/Applications/Santa.app/Contents/MacOS/Santa",
|
||||
@"/Applications/Santa.app/Contents/MacOS/santactl",
|
||||
@"/Applications/Santa.app/Contents/MacOS/santabundleservice",
|
||||
@"/Applications/Santa.app/Contents/MacOS/santametricservice",
|
||||
@"/Applications/Santa.app/Contents/MacOS/santasyncservice",
|
||||
]];
|
||||
|
||||
// Combine the fallback default mute set and Santa-curated set
|
||||
NSMutableSet *superSet = [NSMutableSet setWithSet:fallbackDefaultMuteSet];
|
||||
[superSet unionSet:santaDefinedCriticalPaths];
|
||||
|
||||
if (@available(macOS 12.0, *)) {
|
||||
// Attempt to add the real default mute set
|
||||
addPathsFromDefaultMuteSet(superSet);
|
||||
}
|
||||
|
||||
criticalPaths = [superSet allObjects];
|
||||
});
|
||||
|
||||
return criticalPaths;
|
||||
}
|
||||
|
||||
- (void)setupSystemCriticalBinaries {
|
||||
@@ -215,6 +277,19 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
|
||||
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.
|
||||
//
|
||||
|
||||
@@ -21,7 +21,8 @@ es_string_token_t MakeStringToken(const NSString *_Nonnull s);
|
||||
|
||||
es_file_t MakeESFile(const char *_Nonnull path);
|
||||
es_process_t MakeESProcess(es_file_t *_Nonnull esFile);
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *_Nonnull instigator, struct timespec ts);
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *_Nonnull instigator,
|
||||
struct timespec ts);
|
||||
CF_EXTERN_C_END
|
||||
|
||||
@class ESMessage;
|
||||
@@ -68,6 +69,21 @@ API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
|
||||
es_handler_block_t _Nonnull handler);
|
||||
|
||||
API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_return_t es_mute_process(es_client_t * _Nonnull client,
|
||||
const audit_token_t * _Nonnull audit_token);
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_return_t es_muted_paths_events(es_client_t *_Nonnull client,
|
||||
es_muted_paths_t *_Nonnull *_Nullable muted_paths);
|
||||
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
void es_release_muted_paths(es_muted_paths_t *_Nonnull muted_paths);
|
||||
#endif
|
||||
|
||||
API_AVAILABLE(macos(10.15))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_respond_result_t es_respond_auth_result(es_client_t *_Nonnull client,
|
||||
|
||||
@@ -45,7 +45,8 @@ es_process_t MakeESProcess(es_file_t *esFile) {
|
||||
return esProc;
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator, struct timespec ts) {
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator,
|
||||
struct timespec ts) {
|
||||
es_message_t esMsg = {};
|
||||
|
||||
esMsg.time = ts;
|
||||
@@ -205,6 +206,18 @@ CF_EXTERN_C_END
|
||||
[self.clients addObject:mockClient];
|
||||
}
|
||||
|
||||
- (BOOL)removeClient:(es_client_t *_Nonnull)client {
|
||||
MockESClient *clientToRemove = [self findClient:client];
|
||||
|
||||
if (!clientToRemove) {
|
||||
NSLog(@"Attempted to remove unknown mock es client.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.clients removeObject:clientToRemove];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)triggerHandler:(es_message_t *_Nonnull)msg {
|
||||
for (MockESClient *client in self.clients) {
|
||||
if (client.subscriptions[msg->event_type]) {
|
||||
@@ -233,17 +246,24 @@ CF_EXTERN_C_END
|
||||
return ES_RESPOND_RESULT_SUCCESS;
|
||||
};
|
||||
|
||||
- (MockESClient *)findClient:(es_client_t *)client {
|
||||
for (MockESClient *c in self.clients) {
|
||||
// Since we're mocking out a C interface and using this exact pointer as our
|
||||
// client identifier, only check for pointer equality.
|
||||
if (client == (__bridge es_client_t *)c) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setSubscriptions:(const es_event_type_t *_Nonnull)events
|
||||
event_count:(uint32_t)event_count
|
||||
value:(NSNumber *)value
|
||||
client:(es_client_t *)client {
|
||||
@synchronized(self) {
|
||||
MockESClient *toUpdate = nil;
|
||||
for (MockESClient *c in self.clients) {
|
||||
if (client == (__bridge es_client_t *)c) {
|
||||
toUpdate = c;
|
||||
}
|
||||
}
|
||||
MockESClient *toUpdate = [self findClient:client];
|
||||
|
||||
if (toUpdate == nil) {
|
||||
NSLog(@"setting subscription for unknown client");
|
||||
return;
|
||||
@@ -281,9 +301,36 @@ es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
|
||||
return ES_NEW_CLIENT_RESULT_SUCCESS;
|
||||
};
|
||||
|
||||
es_return_t es_mute_process(es_client_t * _Nonnull client,
|
||||
const audit_token_t * _Nonnull audit_token) {
|
||||
return ES_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_return_t es_muted_paths_events(es_client_t *_Nonnull client,
|
||||
es_muted_paths_t *_Nonnull *_Nullable muted_paths) {
|
||||
es_muted_paths_t *tmp = (es_muted_paths_t *)malloc(sizeof(es_muted_paths_t));
|
||||
|
||||
tmp->count = 0;
|
||||
*muted_paths = (es_muted_paths_t *_Nullable)tmp;
|
||||
|
||||
return ES_RETURN_SUCCESS;
|
||||
};
|
||||
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
void es_release_muted_paths(es_muted_paths_t *_Nonnull muted_paths) {
|
||||
free(muted_paths);
|
||||
}
|
||||
#endif
|
||||
|
||||
API_AVAILABLE(macos(10.15))
|
||||
API_UNAVAILABLE(ios, tvos, watchos) es_return_t es_delete_client(es_client_t *_Nullable client) {
|
||||
[[MockEndpointSecurity mockEndpointSecurity] reset];
|
||||
if (![[MockEndpointSecurity mockEndpointSecurity] removeClient:client]) {
|
||||
return ES_RETURN_ERROR;
|
||||
}
|
||||
return ES_RETURN_SUCCESS;
|
||||
};
|
||||
|
||||
|
||||
@@ -29,21 +29,34 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
}
|
||||
|
||||
@implementation SNTCachingEndpointSecurityManager {
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *_decisionCache;
|
||||
// Create 2 separate caches, mapping from the (filesysem + vnode ID) to a decision with a timestamp.
|
||||
// The root cache is for decisions on the root volume, which can never be unmounted and the other
|
||||
// is for executions from all other volumes. This cache will be emptied if any volume is unmounted.
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *_rootDecisionCache;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *_nonRootDecisionCache;
|
||||
uint64_t _rootVnodeID;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// TODO(rah): Consider splitting into root/non-root cache
|
||||
_decisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
_rootDecisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
_nonRootDecisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
|
||||
// Store the filesystem ID of the root vnode for split-cache usage.
|
||||
// If the stat fails for any reason _rootVnodeID will be 0 and all decisions will be in a single cache.
|
||||
struct stat rootStat;
|
||||
if (stat("/", &rootStat) == 0) {
|
||||
_rootVnodeID = (uint64_t)rootStat.st_dev;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_decisionCache) delete _decisionCache;
|
||||
if (_rootDecisionCache) delete _rootDecisionCache;
|
||||
if (_nonRootDecisionCache) delete _nonRootDecisionCache;
|
||||
}
|
||||
|
||||
- (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
|
||||
@@ -120,6 +133,7 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
- (void)addToCache:(santa_vnode_id_t)identifier
|
||||
decision:(santa_action_t)decision
|
||||
currentTicks:(uint64_t)microsecs {
|
||||
auto _decisionCache = [self cacheForVnodeID:identifier];
|
||||
switch (decision) {
|
||||
case ACTION_REQUEST_BINARY:
|
||||
_decisionCache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
|
||||
@@ -150,25 +164,21 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
}
|
||||
|
||||
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly API_AVAILABLE(macos(10.15)) {
|
||||
_decisionCache->clear();
|
||||
_nonRootDecisionCache->clear();
|
||||
if (!nonRootOnly) _rootDecisionCache->clear();
|
||||
if (!self.connectionEstablished) return YES; // if not connected, there's nothing to flush.
|
||||
return es_clear_cache(self.client) == ES_CLEAR_CACHE_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheCounts {
|
||||
return @[ @(_decisionCache->count()), @(0) ];
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheBucketCount {
|
||||
// TODO: add this, maybe.
|
||||
return nil;
|
||||
return @[ @(_rootDecisionCache->count()), @(_nonRootDecisionCache->count()) ];
|
||||
}
|
||||
|
||||
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
|
||||
auto result = ACTION_UNSET;
|
||||
uint64_t decision_time = 0;
|
||||
|
||||
uint64_t cache_val = _decisionCache->get(vnodeID);
|
||||
uint64_t cache_val = [self cacheForVnodeID:vnodeID]->get(vnodeID);
|
||||
if (cache_val == 0) return result;
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
@@ -179,7 +189,7 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
auto expiry_time = decision_time + (500 * 100000); // kMaxCacheDenyTimeMilliseconds
|
||||
if (expiry_time < GetCurrentUptime()) {
|
||||
_decisionCache->remove(vnodeID);
|
||||
[self cacheForVnodeID:vnodeID]->remove(vnodeID);
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
}
|
||||
@@ -188,9 +198,13 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
}
|
||||
|
||||
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeID {
|
||||
_decisionCache->remove(vnodeID);
|
||||
[self cacheForVnodeID:vnodeID]->remove(vnodeID);
|
||||
// TODO(rah): Look at a replacement for wakeup(), maybe NSCondition
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (SantaCache<santa_vnode_id_t, uint64_t> *)cacheForVnodeID:(santa_vnode_id_t)vnodeID {
|
||||
return (vnodeID.fsid == _rootVnodeID || _rootVnodeID == 0) ? _rootDecisionCache : _nonRootDecisionCache;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -170,6 +170,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
es_event_type_t events[] = {
|
||||
ES_EVENT_TYPE_AUTH_MOUNT,
|
||||
ES_EVENT_TYPE_AUTH_REMOUNT,
|
||||
};
|
||||
|
||||
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
|
||||
@@ -199,44 +200,77 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return;
|
||||
}
|
||||
|
||||
long mountMode = m->event.mount.statfs->f_flags;
|
||||
struct statfs *eventStatFS;
|
||||
BOOL isRemount = NO;
|
||||
|
||||
switch (m->event_type) {
|
||||
case ES_EVENT_TYPE_AUTH_MOUNT: eventStatFS = m->event.mount.statfs; break;
|
||||
case ES_EVENT_TYPE_AUTH_REMOUNT:
|
||||
eventStatFS = m->event.remount.statfs;
|
||||
isRemount = YES;
|
||||
break;
|
||||
default:
|
||||
LOGE(@"Unexpected Event Type passed to DeviceManager handleAuthMount: %d", m->event_type);
|
||||
// Fail closed.
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
assert(0 && "SNTDeviceManager: unexpected event type");
|
||||
return;
|
||||
}
|
||||
|
||||
long mountMode = eventStatFS->f_flags;
|
||||
pid_t pid = audit_token_to_pid(m->process->audit_token);
|
||||
LOGI(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
|
||||
LOGD(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
|
||||
m->process->executable->path.data, pid, mountMode);
|
||||
|
||||
DADiskRef disk =
|
||||
DADiskCreateFromBSDName(NULL, self.diskArbSession, m->event.mount.statfs->f_mntfromname);
|
||||
DADiskRef disk = DADiskCreateFromBSDName(NULL, self.diskArbSession, eventStatFS->f_mntfromname);
|
||||
CFAutorelease(disk);
|
||||
|
||||
// TODO(tnek): Log all of the other attributes available in diskInfo into a structured log format.
|
||||
NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk));
|
||||
BOOL isInternal = [diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceInternalKey] boolValue];
|
||||
BOOL isRemovable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaRemovableKey] boolValue];
|
||||
BOOL isUSB =
|
||||
[diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey] isEqualTo:@"USB"];
|
||||
BOOL isEjectable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaEjectableKey] boolValue];
|
||||
NSString *protocol = diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey];
|
||||
BOOL isUSB = [protocol isEqualToString:@"USB"];
|
||||
BOOL isVirtual = [protocol isEqualToString: @"Virtual Interface"];
|
||||
|
||||
if (!isRemovable || !isUSB) {
|
||||
NSString *kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey];
|
||||
|
||||
// TODO: check kind and protocol for banned things (e.g. MTP).
|
||||
LOGD(@"SNTDeviceManager: DiskInfo Protocol: %@ Kind: %@ isInternal: %d isRemovable: %d "
|
||||
@"isEjectable: %d",
|
||||
protocol, kind, isInternal, isRemovable, isEjectable);
|
||||
|
||||
// If the device is internal or virtual we are okay with the operation. We
|
||||
// also are okay with operations for devices that are non-removal as long as
|
||||
// they are NOT a USB device.
|
||||
if (isInternal || isVirtual || (!isRemovable && !isEjectable && !isUSB)) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
return;
|
||||
}
|
||||
|
||||
SNTDeviceEvent *event = [[SNTDeviceEvent alloc]
|
||||
initWithOnName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntonname]
|
||||
fromName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntfromname]];
|
||||
initWithOnName:[NSString stringWithUTF8String:eventStatFS->f_mntonname]
|
||||
fromName:[NSString stringWithUTF8String:eventStatFS->f_mntfromname]];
|
||||
|
||||
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
|
||||
|
||||
if (shouldRemount) {
|
||||
event.remountArgs = self.remountArgs;
|
||||
long remountOpts = mountArgsToMask(self.remountArgs);
|
||||
if (mountMode & remountOpts) {
|
||||
|
||||
LOGD(@"SNTDeviceManager: mountMode: %@", maskToMountArgs(mountMode));
|
||||
LOGD(@"SNTDeviceManager: remountOpts: %@", maskToMountArgs(remountOpts));
|
||||
|
||||
if ((mountMode & remountOpts) == remountOpts && !isRemount) {
|
||||
LOGD(@"SNTDeviceManager: Allowing as mount as flags match remountOpts");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
return;
|
||||
}
|
||||
|
||||
long newMode = mountMode | remountOpts;
|
||||
LOGI(@"SNTDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)",
|
||||
m->event.mount.statfs->f_mntfromname, m->event.mount.statfs->f_mntonname, mountMode,
|
||||
newMode);
|
||||
eventStatFS->f_mntfromname, eventStatFS->f_mntonname, mountMode, newMode);
|
||||
[self remount:disk mountMode:newMode];
|
||||
}
|
||||
|
||||
@@ -270,26 +304,34 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// ES will kill our whole client if we don't meet the es_message auth deadline, so we try to
|
||||
// gracefully handle it with a deny-by-default in the worst-case before it can do that.
|
||||
// This isn't an issue for notify events, so we're in no rush for those.
|
||||
std::shared_ptr<std::atomic<bool>> responded;
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
responded = std::make_shared<std::atomic<bool>>(false);
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
|
||||
dispatch_semaphore_t processingSema = dispatch_semaphore_create(0);
|
||||
// Add 1 to the processing semaphore. We're not creating it with a starting
|
||||
// value of 1 because that requires that the semaphore is not deallocated
|
||||
// until its value matches the starting value, which we don't need.
|
||||
dispatch_semaphore_signal(processingSema);
|
||||
dispatch_semaphore_t deadlineExpiredSema = dispatch_semaphore_create(0);
|
||||
|
||||
if (mc->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
dispatch_after(timeout, self.esAuthQueue, ^(void) {
|
||||
if (responded->load()) return;
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Handler already responded, nothing to do.
|
||||
return;
|
||||
}
|
||||
LOGE(@"SNTDeviceManager: deadline reached: deny pid=%d ret=%d",
|
||||
audit_token_to_pid(m->process->audit_token),
|
||||
es_respond_auth_result(c, m, ES_AUTH_RESULT_DENY, false));
|
||||
audit_token_to_pid(mc->process->audit_token),
|
||||
es_respond_auth_result(c, mc, ES_AUTH_RESULT_DENY, false));
|
||||
dispatch_semaphore_signal(deadlineExpiredSema);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(tnek): migrate to es_retain_message.
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
dispatch_async(self.esAuthQueue, ^{
|
||||
[self handleESMessage:m withClient:c];
|
||||
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
responded->store(true);
|
||||
[self handleESMessage:mc withClient:c];
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Deadline expired, wait for deadline block to finish.
|
||||
dispatch_semaphore_wait(deadlineExpiredSema, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
es_free_message(mc);
|
||||
});
|
||||
}
|
||||
@@ -297,16 +339,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)handleESMessage:(const es_message_t *)m
|
||||
withClient:(es_client_t *)c API_AVAILABLE(macos(10.15)) {
|
||||
switch (m->event_type) {
|
||||
case ES_EVENT_TYPE_AUTH_MOUNT: {
|
||||
[self handleAuthMount:m withClient:c];
|
||||
// Intentional fallthrough
|
||||
case ES_EVENT_TYPE_AUTH_REMOUNT: {
|
||||
[[fallthrough]];
|
||||
}
|
||||
// TODO(tnek): log any extra data here about mounts.
|
||||
case ES_EVENT_TYPE_NOTIFY_MOUNT: {
|
||||
case ES_EVENT_TYPE_AUTH_MOUNT: {
|
||||
[self handleAuthMount:m withClient:c];
|
||||
break;
|
||||
}
|
||||
default: LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
|
||||
|
||||
default:
|
||||
LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
fclose(stdout);
|
||||
}
|
||||
|
||||
- (ESResponse *)triggerTestMount:(SNTDeviceManager *)deviceManager
|
||||
mockES:(MockEndpointSecurity *)mockES
|
||||
mockDA:(MockDiskArbitration *)mockDA {
|
||||
- (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager
|
||||
mockES:(MockEndpointSecurity *)mockES
|
||||
mockDA:(MockDiskArbitration *)mockDA
|
||||
eventType:(es_event_type_t)eventType
|
||||
diskInfoOverrides:(NSDictionary *)diskInfo {
|
||||
if (!deviceManager.subscribed) {
|
||||
// [deviceManager listen] is synchronous, but we want to asynchronously dispatch it
|
||||
// with an enforced timeout to ensure that we never run into issues where the client
|
||||
@@ -84,26 +86,39 @@
|
||||
@"DAMediaBSDName" : test_mntfromname,
|
||||
};
|
||||
|
||||
if (diskInfo != nil) {
|
||||
NSMutableDictionary *mergedDiskDescription = [disk.diskDescription mutableCopy];
|
||||
for (NSString *key in diskInfo) {
|
||||
mergedDiskDescription[key] = diskInfo[key];
|
||||
}
|
||||
disk.diskDescription = (NSDictionary *)mergedDiskDescription;
|
||||
}
|
||||
|
||||
[mockDA insert:disk bsdName:test_mntfromname];
|
||||
|
||||
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
|
||||
m.binaryPath = @"/System/Library/Filesystems/msdos.fs/Contents/Resources/mount_msdos";
|
||||
m.message->action_type = ES_ACTION_TYPE_AUTH;
|
||||
m.message->event_type = ES_EVENT_TYPE_AUTH_MOUNT;
|
||||
m.message->event = (es_events_t){.mount = {.statfs = fs}};
|
||||
m.message->event_type = eventType;
|
||||
if (eventType == ES_EVENT_TYPE_AUTH_MOUNT) {
|
||||
m.message->event = (es_events_t){.mount = {.statfs = fs}};
|
||||
} else {
|
||||
m.message->event = (es_events_t){.remount = {.statfs = fs}};
|
||||
}
|
||||
}];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
|
||||
XCTestExpectation *mountExpectation =
|
||||
[self expectationWithDescription:@"Wait for response from ES"];
|
||||
__block ESResponse *got;
|
||||
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
[mockES registerResponseCallback:eventType
|
||||
withCallback:^(ESResponse *r) {
|
||||
got = r;
|
||||
[expectation fulfill];
|
||||
[mountExpectation fulfill];
|
||||
}];
|
||||
|
||||
[mockES triggerHandler:m.message];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:60.0];
|
||||
[self waitForExpectations:@[ mountExpectation ] timeout:60.0];
|
||||
free(fs);
|
||||
|
||||
return got;
|
||||
@@ -118,7 +133,12 @@
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = NO;
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
|
||||
}
|
||||
|
||||
@@ -145,7 +165,11 @@
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, YES);
|
||||
@@ -179,7 +203,11 @@
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
|
||||
@@ -190,4 +218,74 @@
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testEnsureRemountsCannotChangePerms {
|
||||
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
|
||||
[mockES reset];
|
||||
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
[mockDA reset];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
|
||||
|
||||
__block NSString *gotmntonname, *gotmntfromname;
|
||||
__block NSArray<NSString *> *gotRemountedArgs;
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
gotRemountedArgs = event.remountArgs;
|
||||
gotmntonname = event.mntonname;
|
||||
gotmntfromname = event.mntfromname;
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_REMOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, YES);
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:10.0];
|
||||
|
||||
XCTAssertEqualObjects(gotRemountedArgs, deviceManager.remountArgs);
|
||||
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testEnsureDMGsDoNotPrompt {
|
||||
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
|
||||
[mockES reset];
|
||||
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
[mockDA reset];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
XCTFail(@"Should not be called");
|
||||
};
|
||||
|
||||
NSDictionary *diskInfo = @{
|
||||
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey: @"Virtual Interface",
|
||||
(__bridge NSString *)kDADiskDescriptionDeviceModelKey: @"Disk Image",
|
||||
(__bridge NSString *)kDADiskDescriptionMediaNameKey: @"disk image",
|
||||
};
|
||||
|
||||
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:diskInfo];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
|
||||
XCTAssertEqual(mockDA.wasRemounted, NO);
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -34,7 +34,6 @@ static const pid_t PID_MAX = 99999;
|
||||
@property(nonatomic) SNTPrefixTree *prefixTree;
|
||||
@property(nonatomic, readonly) dispatch_queue_t esAuthQueue;
|
||||
@property(nonatomic, readonly) dispatch_queue_t esNotifyQueue;
|
||||
@property(nonatomic, readonly) pid_t selfPID;
|
||||
|
||||
@end
|
||||
|
||||
@@ -45,11 +44,10 @@ static const pid_t PID_MAX = 99999;
|
||||
if (self) {
|
||||
// To avoid nil deref from es_events arriving before listenForDecisionRequests or
|
||||
// listenForLogRequests in the MockEndpointSecurity testing util.
|
||||
_decisionCallback = ^(santa_message_t) {
|
||||
};
|
||||
_logCallback = ^(santa_message_t) {
|
||||
};
|
||||
_decisionCallback = ^(santa_message_t) {};
|
||||
_logCallback = ^(santa_message_t) {};
|
||||
[self establishClient];
|
||||
[self muteSelf];
|
||||
_prefixTree = new SNTPrefixTree();
|
||||
_esAuthQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.es_auth", DISPATCH_QUEUE_CONCURRENT);
|
||||
@@ -58,7 +56,6 @@ static const pid_t PID_MAX = 99999;
|
||||
_esNotifyQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.es_notify", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(_esNotifyQueue, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
|
||||
_selfPID = getpid();
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -72,6 +69,24 @@ static const pid_t PID_MAX = 99999;
|
||||
if (_prefixTree) delete _prefixTree;
|
||||
}
|
||||
|
||||
- (void)muteSelf {
|
||||
audit_token_t myAuditToken;
|
||||
mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT;
|
||||
if (task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)&myAuditToken, &count) ==
|
||||
KERN_SUCCESS) {
|
||||
if (es_mute_process(self.client, &myAuditToken) == ES_RETURN_SUCCESS) {
|
||||
return;
|
||||
} else {
|
||||
LOGE(@"Failed to mute this client's process, its events will not be muted.");
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Failed to fetch this client's audit token. Its events will not be muted.");
|
||||
}
|
||||
|
||||
// If we get here, Santa was unable to mute itself. Assume transitory and bail.
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
- (void)establishClient API_AVAILABLE(macos(10.15)) {
|
||||
while (!self.client) {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
@@ -86,9 +101,7 @@ static const pid_t PID_MAX = 99999;
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
}
|
||||
if (self.selfPID != pid) {
|
||||
LOGD(@"Skipping event type: 0x%x from es_client pid: %d", m->event_type, pid);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,9 +127,10 @@ static const pid_t PID_MAX = 99999;
|
||||
// Create a transitive rule if the file was modified by a running compiler
|
||||
if ([self isCompilerPID:pid]) {
|
||||
santa_message_t sm = {};
|
||||
BOOL truncated = [SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)];
|
||||
BOOL truncated =
|
||||
[SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)];
|
||||
if (truncated) {
|
||||
LOGE(@"CLOSE: error creating transitive rule, the path is truncated: path=%s pid=%d",
|
||||
sm.path, pid);
|
||||
@@ -182,7 +196,12 @@ static const pid_t PID_MAX = 99999;
|
||||
case ES_EVENT_TYPE_NOTIFY_UNMOUNT: {
|
||||
// Flush the non-root cache - the root disk cannot be unmounted
|
||||
// so it isn't necessary to flush its cache.
|
||||
[self flushCacheNonRootOnly:YES];
|
||||
//
|
||||
// Flushing the cache calls back into ES. We need to perform this off the handler thread
|
||||
// otherwise we could potentially deadlock.
|
||||
dispatch_async(self.esAuthQueue, ^() {
|
||||
[self flushCacheNonRootOnly:YES];
|
||||
});
|
||||
|
||||
// Skip all other processing
|
||||
return;
|
||||
@@ -210,30 +229,42 @@ static const pid_t PID_MAX = 99999;
|
||||
|
||||
switch (m->action_type) {
|
||||
case ES_ACTION_TYPE_AUTH: {
|
||||
// Copy the message
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
|
||||
dispatch_semaphore_t processingSema = dispatch_semaphore_create(0);
|
||||
// Add 1 to the processing semaphore. We're not creating it with a starting
|
||||
// value of 1 because that requires that the semaphore is not deallocated
|
||||
// until its value matches the starting value, which we don't need.
|
||||
dispatch_semaphore_signal(processingSema);
|
||||
dispatch_semaphore_t deadlineExpiredSema = dispatch_semaphore_create(0);
|
||||
|
||||
// Create a timer to deny the execution 5 seconds before the deadline,
|
||||
// if a response hasn't already been sent. This block will still be enqueued if
|
||||
// the the deadline - 5 secs is < DISPATCH_TIME_NOW.
|
||||
// As of 10.15.5, a typical deadline is 60 seconds.
|
||||
auto responded = std::make_shared<std::atomic<bool>>(false);
|
||||
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -5), self.esAuthQueue, ^(void) {
|
||||
if (responded->load()) return;
|
||||
LOGE(@"Deadline reached: deny pid=%d ret=%d", pid,
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false));
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Handler has already responded, nothing to do.
|
||||
return;
|
||||
}
|
||||
LOGE(@"SNTEndpointSecurityManager: deadline reached: deny pid=%d ret=%d", pid,
|
||||
es_respond_auth_result(self.client, mc, ES_AUTH_RESULT_DENY, false));
|
||||
dispatch_semaphore_signal(deadlineExpiredSema);
|
||||
});
|
||||
|
||||
// Copy the message and return control back to ES
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
// Dispatch off to the handler and return control to ES.
|
||||
dispatch_async(self.esAuthQueue, ^{
|
||||
[self messageHandler:mc];
|
||||
responded->store(true);
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Deadline expired, wait for deadline block to finish.
|
||||
dispatch_semaphore_wait(deadlineExpiredSema, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
es_free_message(mc);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ES_ACTION_TYPE_NOTIFY: {
|
||||
// Don't log fileop events from com.google.santa.daemon
|
||||
if (self.selfPID == pid && m->event_type != ES_EVENT_TYPE_NOTIFY_EXEC) return;
|
||||
|
||||
// Copy the message and return control back to ES
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
dispatch_async(self.esNotifyQueue, ^{
|
||||
@@ -288,6 +319,10 @@ static const pid_t PID_MAX = 99999;
|
||||
targetFile = m->event.exec.target->executable;
|
||||
targetProcess = m->event.exec.target;
|
||||
callback = self.decisionCallback;
|
||||
|
||||
[SNTEndpointSecurityManager populateBufferFromESFile:m->process->tty
|
||||
buffer:sm.ttypath
|
||||
size:sizeof(sm.ttypath)];
|
||||
break;
|
||||
}
|
||||
case ES_EVENT_TYPE_NOTIFY_EXEC: {
|
||||
@@ -314,8 +349,7 @@ static const pid_t PID_MAX = 99999;
|
||||
NSString *path = [[NSString alloc] initWithBytes:pathToken.data
|
||||
length:pathToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if ([self isDatabasePath:path] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:path]) {
|
||||
LOGW(@"Preventing attempt to delete Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -329,8 +363,7 @@ static const pid_t PID_MAX = 99999;
|
||||
length:pathToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
if ([self isDatabasePath:path] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:path]) {
|
||||
LOGW(@"Preventing attempt to rename Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -340,8 +373,7 @@ static const pid_t PID_MAX = 99999;
|
||||
NSString *destPath = [[NSString alloc] initWithBytes:destToken.data
|
||||
length:destToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if ([self isDatabasePath:destPath] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:destPath]) {
|
||||
LOGW(@"Preventing attempt to overwrite Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -544,6 +576,7 @@ static const pid_t PID_MAX = 99999;
|
||||
// Returns YES if the path was truncated.
|
||||
// The populated buffer will be NUL terminated.
|
||||
+ (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
|
||||
if (file == NULL) return NO;
|
||||
return [SNTEndpointSecurityManager populateBufferFromString:file->path.data
|
||||
buffer:buffer
|
||||
size:size];
|
||||
|
||||
@@ -447,7 +447,11 @@
|
||||
logger = [[SNTProtobufEventLog alloc] init];
|
||||
break;
|
||||
}
|
||||
default: logger = nil;
|
||||
case SNTEventLogTypeNull: {
|
||||
// Messages sent to nil objects do nothing, which is perfect for a null logger.
|
||||
logger = nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return logger;
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
exec.argsArray = [(__bridge NSArray *)message.args_array mutableCopy];
|
||||
exec.machineId =
|
||||
[[SNTConfigurator configurator] enableMachineIDDecoration] ? self.machineID : nil;
|
||||
exec.teamId = cd.teamID;
|
||||
|
||||
[self logWithSantaMessage:&message
|
||||
wrapper:^(SNTPBSantaMessage *sm) {
|
||||
|
||||
@@ -170,11 +170,15 @@
|
||||
|
||||
[outLog appendFormat:@"|sha256=%@", cd.sha256];
|
||||
|
||||
if (cd.certSHA256) {
|
||||
if (cd.certSHA256.length) {
|
||||
[outLog appendFormat:@"|cert_sha256=%@|cert_cn=%@", cd.certSHA256,
|
||||
[self sanitizeString:cd.certCommonName]];
|
||||
}
|
||||
|
||||
if (cd.teamID.length) {
|
||||
[outLog appendFormat:@"|teamid=%@", cd.teamID];
|
||||
}
|
||||
|
||||
if (cd.quarantineURL) {
|
||||
[outLog appendFormat:@"|quarantine_url=%@", [self sanitizeString:cd.quarantineURL]];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/SNTApplication.h"
|
||||
#import "Source/santad/SNTApplicationCoreMetrics.h"
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@@ -26,6 +25,7 @@
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/common/SNTXPCMetricServiceInterface.h"
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/common/SNTXPCSyncServiceInterface.h"
|
||||
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
|
||||
#import "Source/santad/DataLayer/SNTEventTable.h"
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
@@ -34,6 +34,7 @@
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
#import "Source/santad/Logs/SNTEventLog.h"
|
||||
#import "Source/santad/SNTApplicationCoreMetrics.h"
|
||||
#import "Source/santad/SNTCompilerController.h"
|
||||
#import "Source/santad/SNTDaemonControlController.h"
|
||||
#import "Source/santad/SNTDatabaseController.h"
|
||||
@@ -48,9 +49,9 @@
|
||||
@property SNTDeviceManager *deviceManager;
|
||||
@property MOLXPCConnection *controlConnection;
|
||||
@property SNTNotificationQueue *notQueue;
|
||||
@property pid_t syncdPID;
|
||||
@property MOLXPCConnection *metricsConnection;
|
||||
@property dispatch_source_t metricsTimer;
|
||||
@property SNTSyncdQueue *syncdQueue;
|
||||
@end
|
||||
|
||||
@implementation SNTApplication
|
||||
@@ -114,12 +115,8 @@
|
||||
[self.eventProvider fileModificationPrefixFilterAdd:[configurator fileChangesPrefixFilters]];
|
||||
});
|
||||
|
||||
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
|
||||
|
||||
// Restart santactl if it goes down
|
||||
syncdQueue.invalidationHandler = ^{
|
||||
[self startSyncd];
|
||||
};
|
||||
self.notQueue = [[SNTNotificationQueue alloc] init];
|
||||
self.syncdQueue = [[SNTSyncdQueue alloc] init];
|
||||
|
||||
// Listen for actionable config changes.
|
||||
NSKeyValueObservingOptions bits = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld);
|
||||
@@ -160,7 +157,7 @@
|
||||
SNTDaemonControlController *dc =
|
||||
[[SNTDaemonControlController alloc] initWithEventProvider:_eventProvider
|
||||
notificationQueue:self.notQueue
|
||||
syncdQueue:syncdQueue];
|
||||
syncdQueue:self.syncdQueue];
|
||||
|
||||
_controlConnection =
|
||||
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceID]];
|
||||
@@ -178,9 +175,9 @@
|
||||
ruleTable:ruleTable
|
||||
eventTable:eventTable
|
||||
notifierQueue:self.notQueue
|
||||
syncdQueue:syncdQueue];
|
||||
// Start up santactl as a daemon if a sync server exists.
|
||||
[self startSyncd];
|
||||
syncdQueue:self.syncdQueue];
|
||||
// Establish a connection with the sync service if a sync server exists.
|
||||
[self establishSyncServiceConnection];
|
||||
|
||||
if (!_execController) return nil;
|
||||
|
||||
@@ -313,27 +310,25 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
|
||||
dispatch_source_cancel(_metricsTimer);
|
||||
}
|
||||
|
||||
- (void)startSyncd {
|
||||
- (void)establishSyncServiceConnection {
|
||||
// The syncBaseURL check is here to stop retrying if the sync server is removed.
|
||||
// See -[syncBaseURLDidChange:] for more info.
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[self stopSyncd];
|
||||
self.syncdPID = fork();
|
||||
if (self.syncdPID == -1) {
|
||||
LOGI(@"Failed to fork");
|
||||
self.syncdPID = 0;
|
||||
} else if (self.syncdPID == 0) {
|
||||
// The santactl executable will drop privileges just after the XPC
|
||||
// connection has been estabilished; this is done this way so that
|
||||
// the XPC authentication can occur
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
|
||||
}
|
||||
LOGI(@"santactl started with pid: %i", self.syncdPID);
|
||||
}
|
||||
|
||||
- (void)stopSyncd {
|
||||
if (!self.syncdPID) return;
|
||||
int ret = kill(self.syncdPID, SIGKILL);
|
||||
LOGD(@"kill(%i, 9) = %i", self.syncdPID, ret);
|
||||
self.syncdPID = 0;
|
||||
MOLXPCConnection *ss = [SNTXPCSyncServiceInterface configuredConnection];
|
||||
// This will handle retying connection establishment if there are issues with the service
|
||||
// during initialization (missing binary, malformed plist, bad code signature, etc.).
|
||||
// Once those issues are resolved the connection will establish.
|
||||
// This will also handle re-establishment if the service crashes or is killed.
|
||||
WEAKIFY(self);
|
||||
ss.invalidationHandler = ^(void) {
|
||||
STRONGIFY(self);
|
||||
self.syncdQueue.syncConnection.invalidationHandler = nil;
|
||||
[self performSelectorOnMainThread:@selector(establishSyncServiceConnection)
|
||||
withObject:nil
|
||||
waitUntilDone:YES];
|
||||
};
|
||||
[ss resume]; // If there are issues establishing the connection resume will block for 2 seconds.
|
||||
self.syncdQueue.syncConnection = ss;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
@@ -418,14 +413,15 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
|
||||
|
||||
- (void)syncBaseURLDidChange:(NSURL *)syncBaseURL {
|
||||
if (syncBaseURL) {
|
||||
LOGI(@"Starting santactl with new SyncBaseURL: %@", syncBaseURL);
|
||||
LOGI(@"Establishing a new sync service connection with SyncBaseURL: %@", syncBaseURL);
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:[SNTConfigurator configurator]
|
||||
selector:@selector(clearSyncState)
|
||||
object:nil];
|
||||
[self startSyncd];
|
||||
[[self.syncdQueue.syncConnection remoteObjectProxy] spindown];
|
||||
[self establishSyncServiceConnection];
|
||||
} else {
|
||||
LOGI(@"SyncBaseURL removed, killing santactl pid: %i", self.syncdPID);
|
||||
[self stopSyncd];
|
||||
LOGI(@"SyncBaseURL removed, spinning down sync service");
|
||||
[[self.syncdQueue.syncConnection remoteObjectProxy] spindown];
|
||||
// Keep the syncState active for 10 min in case com.apple.ManagedClient is flapping.
|
||||
[[SNTConfigurator configurator] performSelector:@selector(clearSyncState)
|
||||
withObject:nil
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
#import "Source/common/SNTXPCSyncServiceInterface.h"
|
||||
#import "Source/santad/DataLayer/SNTEventTable.h"
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
@@ -43,7 +43,6 @@ double watchdogCPUPeak = 0;
|
||||
double watchdogRAMPeak = 0;
|
||||
|
||||
@interface SNTDaemonControlController ()
|
||||
@property NSString *_syncXsrfToken;
|
||||
@property SNTPolicyProcessor *policyProcessor;
|
||||
@property id<SNTEventProvider> eventProvider;
|
||||
@property SNTNotificationQueue *notQueue;
|
||||
@@ -85,10 +84,6 @@ double watchdogRAMPeak = 0;
|
||||
reply([self.eventProvider checkCache:vnodeID]);
|
||||
}
|
||||
|
||||
- (void)driverConnectionEstablished:(void (^)(BOOL))reply {
|
||||
reply(self.eventProvider.connectionEstablished);
|
||||
}
|
||||
|
||||
#pragma mark Database ops
|
||||
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
|
||||
@@ -144,6 +139,10 @@ double watchdogRAMPeak = 0;
|
||||
teamID:teamID]);
|
||||
}
|
||||
|
||||
- (void)staticRuleCount:(void (^)(int64_t count))reply {
|
||||
reply([SNTConfigurator configurator].staticRules.count);
|
||||
}
|
||||
|
||||
#pragma mark Decision Ops
|
||||
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
@@ -173,15 +172,6 @@ double watchdogRAMPeak = 0;
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply {
|
||||
reply(self._syncXsrfToken);
|
||||
}
|
||||
|
||||
- (void)setXsrfToken:(NSString *)token reply:(void (^)(void))reply {
|
||||
self._syncXsrfToken = token;
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply {
|
||||
reply([[SNTConfigurator configurator] fullSyncLastSuccess]);
|
||||
}
|
||||
@@ -252,6 +242,24 @@ double watchdogRAMPeak = 0;
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)enableAllEventUpload:(void (^)(BOOL))reply {
|
||||
reply([SNTConfigurator configurator].enableAllEventUpload);
|
||||
}
|
||||
|
||||
- (void)setEnableAllEventUpload:(BOOL)enabled reply:(void (^)(void))reply {
|
||||
[[SNTConfigurator configurator] setEnableAllEventUpload:enabled];
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)disableUnknownEventUpload:(void (^)(BOOL))reply {
|
||||
reply([SNTConfigurator configurator].disableUnknownEventUpload);
|
||||
}
|
||||
|
||||
- (void)setDisableUnknownEventUpload:(BOOL)enabled reply:(void (^)(void))reply {
|
||||
[[SNTConfigurator configurator] setDisableUnknownEventUpload:enabled];
|
||||
reply();
|
||||
}
|
||||
|
||||
#pragma mark Metrics Ops
|
||||
|
||||
- (void)metrics:(void (^)(NSDictionary *))reply {
|
||||
@@ -272,26 +280,8 @@ double watchdogRAMPeak = 0;
|
||||
|
||||
#pragma mark syncd Ops
|
||||
|
||||
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener {
|
||||
// Only allow one active syncd connection
|
||||
if (self.syncdQueue.syncdConnection) return;
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithListener:listener];
|
||||
c.remoteInterface = [SNTXPCSyncdInterface syncdInterface];
|
||||
c.invalidationHandler = ^{
|
||||
[self.syncdQueue stopSyncingEvents];
|
||||
[self.syncdQueue.syncdConnection invalidate];
|
||||
self.syncdQueue.syncdConnection = nil;
|
||||
if (self.syncdQueue.invalidationHandler) self.syncdQueue.invalidationHandler();
|
||||
};
|
||||
c.acceptedHandler = ^{
|
||||
[self.syncdQueue startSyncingEvents];
|
||||
};
|
||||
[c resume];
|
||||
self.syncdQueue.syncdConnection = c;
|
||||
}
|
||||
|
||||
- (void)pushNotifications:(void (^)(BOOL))reply {
|
||||
[self.syncdQueue.syncdConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
|
||||
[self.syncdQueue.syncConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
|
||||
reply(response);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#import "Source/santad/SNTExecutionController.h"
|
||||
|
||||
#include <copyfile.h>
|
||||
#include <libproc.h>
|
||||
#include <pwd.h>
|
||||
#include <utmpx.h>
|
||||
@@ -135,11 +136,14 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
NSError *fileInfoError;
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
|
||||
if (unlikely(!binInfo)) {
|
||||
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
|
||||
if (config.failClosed && config.clientMode == SNTClientModeLockdown) {
|
||||
LOGE(@"Failed to read file %@: %@ and denying action", @(message.path),
|
||||
fileInfoError.localizedDescription);
|
||||
[self.eventProvider postAction:ACTION_RESPOND_DENY forMessage:message];
|
||||
[self.events incrementForFieldValues:@[ (NSString *)kDenyNoFileInfo ]];
|
||||
} else {
|
||||
LOGE(@"Failed to read file %@: %@ but allowing action", @(message.path),
|
||||
fileInfoError.localizedDescription);
|
||||
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
|
||||
[self.events incrementForFieldValues:@[ (NSString *)kAllowNoFileInfo ]];
|
||||
}
|
||||
@@ -175,7 +179,7 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
[[SNTEventLog logger] cacheDecision:cd];
|
||||
} else {
|
||||
ttyPath = [self ttyPathForPID:message.ppid];
|
||||
ttyPath = @(message.ttypath);
|
||||
}
|
||||
|
||||
// Upgrade the action to ACTION_RESPOND_ALLOW_COMPILER when appropriate, because we want the
|
||||
@@ -191,9 +195,9 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
[self incrementEventCounters:cd.decision];
|
||||
|
||||
// Log to database if necessary.
|
||||
if (cd.decision != SNTEventStateAllowBinary && cd.decision != SNTEventStateAllowCompiler &&
|
||||
cd.decision != SNTEventStateAllowTransitive && cd.decision != SNTEventStateAllowCertificate &&
|
||||
cd.decision != SNTEventStateAllowTeamID && cd.decision != SNTEventStateAllowScope) {
|
||||
if (config.enableAllEventUpload ||
|
||||
(cd.decision == SNTEventStateAllowUnknown && !config.disableUnknownEventUpload) ||
|
||||
(cd.decision & SNTEventStateAllow) == 0) {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
se.occurrenceDate = [[NSDate alloc] init];
|
||||
se.fileSHA256 = cd.sha256;
|
||||
@@ -201,6 +205,7 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
se.decision = cd.decision;
|
||||
|
||||
se.signingChain = cd.certChain;
|
||||
se.teamID = cd.teamID;
|
||||
se.pid = @(message.pid);
|
||||
se.ppid = @(message.ppid);
|
||||
se.parentName = @(message.pname);
|
||||
@@ -300,15 +305,12 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
SNTFileInfo *proxyFi = [self printerProxyFileInfo];
|
||||
if ([proxyFi.SHA256 isEqual:fi.SHA256]) return NO;
|
||||
|
||||
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyFi.path];
|
||||
NSFileHandle *outFh = [NSFileHandle fileHandleForWritingAtPath:fi.path];
|
||||
[outFh writeData:[inFh readDataToEndOfFile]];
|
||||
[inFh closeFile];
|
||||
[outFh truncateFileAtOffset:[outFh offsetInFile]];
|
||||
[outFh synchronizeFile];
|
||||
[outFh closeFile];
|
||||
|
||||
LOGW(@"PrinterProxy workaround applied to %@", fi.path);
|
||||
copyfile_flags_t copyflags = COPYFILE_ALL | COPYFILE_UNLINK;
|
||||
if (copyfile(proxyFi.path.UTF8String, fi.path.UTF8String, NULL, copyflags) != 0) {
|
||||
LOGE(@"Failed to apply PrinterProxy workaround for %@", fi.path);
|
||||
} else {
|
||||
LOGI(@"PrinterProxy workaround applied to: %@", fi.path);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -324,19 +326,6 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
return proxyInfo;
|
||||
}
|
||||
|
||||
- (NSString *)ttyPathForPID:(pid_t)pid {
|
||||
if (pid < 2) return nil; // don't bother even looking for launchd.
|
||||
|
||||
struct proc_bsdinfo taskInfo = {};
|
||||
if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &taskInfo, sizeof(taskInfo)) < 1) return nil;
|
||||
|
||||
// 16-bytes here is for future-proofing. Currently kern.tty.ptmx_max is
|
||||
// limited to 999 so 12 bytes should be enough.
|
||||
char devPath[16] = "/dev/";
|
||||
snprintf(devPath, 16, "/dev/%s", devname(taskInfo.e_tdev, S_IFCHR));
|
||||
return @(devPath);
|
||||
}
|
||||
|
||||
- (void)printMessage:(NSString *)msg toTTY:(NSString *)path {
|
||||
int fd = open(path.UTF8String, O_WRONLY | O_NOCTTY);
|
||||
write(fd, msg.UTF8String, msg.length);
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
|
||||
fclose(stdout);
|
||||
|
||||
[[SNTMetricSet sharedInstance] reset];
|
||||
|
||||
self.mockCodesignChecker = OCMClassMock([MOLCodesignChecker class]);
|
||||
OCMStub([self.mockCodesignChecker alloc]).andReturn(self.mockCodesignChecker);
|
||||
|
||||
@@ -111,7 +113,7 @@
|
||||
break;
|
||||
}
|
||||
|
||||
if (!foundField) {
|
||||
if (!foundField && expectedValue.intValue != 0) {
|
||||
XCTFail(@"failed to find %@ field value", expectedFieldValueName);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +131,7 @@
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:@"AllowBinary" expected:@2];
|
||||
[self checkMetricCounters:@"AllowBinary" expected:@1];
|
||||
}
|
||||
|
||||
- (void)testBinaryBlockRule {
|
||||
@@ -241,7 +243,7 @@
|
||||
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
|
||||
[self checkMetricCounters:@"AllowBinary" expected:@2];
|
||||
[self checkMetricCounters:kAllowTransitive expected:@1];
|
||||
}
|
||||
|
||||
- (void)testBinaryAllowTransitiveRuleDisabled {
|
||||
@@ -262,8 +264,8 @@
|
||||
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
[self checkMetricCounters:kAllowBinary expected:@2];
|
||||
[self checkMetricCounters:kAllowTransitive expected:@1];
|
||||
[self checkMetricCounters:kAllowBinary expected:@0];
|
||||
[self checkMetricCounters:kAllowTransitive expected:@0];
|
||||
}
|
||||
|
||||
- (void)testDefaultDecision {
|
||||
@@ -281,7 +283,7 @@
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
|
||||
[self checkMetricCounters:kBlockUnknown expected:@2];
|
||||
[self checkMetricCounters:kBlockUnknown expected:@1];
|
||||
[self checkMetricCounters:kAllowUnknown expected:@1];
|
||||
}
|
||||
|
||||
@@ -298,7 +300,7 @@
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowNoFileInfo expected:@2];
|
||||
[self checkMetricCounters:kAllowNoFileInfo expected:@1];
|
||||
}
|
||||
|
||||
- (void)testUnreadableFailClosedLockdown {
|
||||
@@ -344,7 +346,7 @@
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowScope expected:@2];
|
||||
[self checkMetricCounters:kAllowScope expected:@1];
|
||||
}
|
||||
|
||||
- (void)testPageZero {
|
||||
@@ -354,7 +356,39 @@
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
[self checkMetricCounters:kBlockUnknown expected:@3];
|
||||
[self checkMetricCounters:kBlockUnknown expected:@1];
|
||||
}
|
||||
|
||||
- (void)testAllEventUpload {
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
OCMExpect([self.mockConfigurator enableAllEventUpload]).andReturn(YES);
|
||||
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateAllow;
|
||||
rule.type = SNTRuleTypeBinary;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
|
||||
.andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
}
|
||||
|
||||
- (void)testDisableUnknownEventUpload {
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
|
||||
OCMExpect([self.mockConfigurator enableAllEventUpload]).andReturn(NO);
|
||||
OCMExpect([self.mockConfigurator disableUnknownEventUpload]).andReturn(YES);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify(never(), [self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
|
||||
[self checkMetricCounters:kAllowUnknown expected:@1];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -21,13 +21,9 @@
|
||||
|
||||
@interface SNTSyncdQueue : NSObject
|
||||
|
||||
@property(nonatomic) MOLXPCConnection *syncdConnection;
|
||||
@property(copy) void (^invalidationHandler)(void);
|
||||
@property(copy) void (^acceptedHandler)(void);
|
||||
@property(nonatomic) MOLXPCConnection *syncConnection;
|
||||
|
||||
- (void)addEvents:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
|
||||
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)startSyncingEvents;
|
||||
- (void)stopSyncingEvents;
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,12 +18,11 @@
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
#import "Source/common/SNTXPCSyncServiceInterface.h"
|
||||
|
||||
@interface SNTSyncdQueue ()
|
||||
@property NSCache<NSString *, NSDate *> *uploadBackoff;
|
||||
@property dispatch_queue_t syncdQueue;
|
||||
@property dispatch_semaphore_t sema;
|
||||
@end
|
||||
|
||||
@implementation SNTSyncdQueue
|
||||
@@ -34,7 +33,6 @@
|
||||
_uploadBackoff = [[NSCache alloc] init];
|
||||
_uploadBackoff.countLimit = 128;
|
||||
_syncdQueue = dispatch_queue_create("com.google.syncd_queue", DISPATCH_QUEUE_SERIAL);
|
||||
_sema = dispatch_semaphore_create(0);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -45,15 +43,14 @@
|
||||
NSString *hash = isFromBundle ? first.fileBundleHash : first.fileSHA256;
|
||||
if (![self backoffForPrimaryHash:hash]) return;
|
||||
[self dispatchBlockOnSyncdQueue:^{
|
||||
[self.syncdConnection.remoteObjectProxy postEventsToSyncServer:events
|
||||
isFromBundle:isFromBundle];
|
||||
[self.syncConnection.remoteObjectProxy postEventsToSyncServer:events fromBundle:isFromBundle];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply {
|
||||
if (![self backoffForPrimaryHash:event.fileBundleHash]) return;
|
||||
[self dispatchBlockOnSyncdQueue:^{
|
||||
[self.syncdConnection.remoteObjectProxy
|
||||
[self.syncConnection.remoteObjectProxy
|
||||
postBundleEventToSyncServer:event
|
||||
reply:^(SNTBundleEventAction action) {
|
||||
// Remove the backoff entry for the initial block event. The same
|
||||
@@ -67,25 +64,10 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)startSyncingEvents {
|
||||
dispatch_semaphore_signal(self.sema);
|
||||
}
|
||||
|
||||
- (void)stopSyncingEvents {
|
||||
self.sema = dispatch_semaphore_create(0);
|
||||
}
|
||||
|
||||
// Hold events for a few seconds to allow santad and santactl to establish connections.
|
||||
// If the connections are not established in time drop the event from the queue.
|
||||
// They will be uploaded during a full sync.
|
||||
- (void)dispatchBlockOnSyncdQueue:(void (^)(void))block {
|
||||
if (!block) return;
|
||||
dispatch_async(self.syncdQueue, ^{
|
||||
if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
|
||||
if (block) block();
|
||||
dispatch_semaphore_signal(self.sema);
|
||||
} else {
|
||||
LOGD(@"Dropping block %@ from com.google.syncd_queue", block);
|
||||
}
|
||||
block();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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.endpoint-security.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -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.daemon</string>
|
||||
<key>com.apple.developer.endpoint-security.client</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>EQHXZ8M8AV</string>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>EQHXZ8M8AV.com.google.santa.daemon</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -3,7 +3,6 @@ load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
@@ -15,6 +14,9 @@ objc_library(
|
||||
"SNTMetricService.m",
|
||||
"main.m",
|
||||
],
|
||||
hdrs = [
|
||||
"SNTMetricService.h",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
@@ -36,7 +38,11 @@ santa_unit_test(
|
||||
structured_resources = ["//Source/santametricservice/Formats:testdata"],
|
||||
deps = [
|
||||
":SNTMetricServiceLib",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
|
||||
"@MOLAuthenticatingURLSession",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
@@ -61,8 +67,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"],
|
||||
|
||||
@@ -2,7 +2,6 @@ load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
@@ -30,6 +29,9 @@ objc_library(
|
||||
"SNTMetricRawJSONFormat.h",
|
||||
"SNTMetricRawJSONFormat.m",
|
||||
],
|
||||
hdrs = [
|
||||
"SNTMetricRawJSONFormat.h",
|
||||
],
|
||||
deps = [
|
||||
":SNTMetricFormat",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -43,6 +45,9 @@ objc_library(
|
||||
"SNTMetricMonarchJSONFormat.h",
|
||||
"SNTMetricMonarchJSONFormat.m",
|
||||
],
|
||||
hdrs = [
|
||||
"SNTMetricMonarchJSONFormat.h",
|
||||
],
|
||||
deps = [
|
||||
":SNTMetricFormat",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -71,6 +76,7 @@ santa_unit_test(
|
||||
deps = [
|
||||
":SNTMetricFormatTestHelper",
|
||||
":SNTMetricMonarchJSONFormat",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||