mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc3177502c | ||
|
|
a49a59b109 | ||
|
|
2c06c39c82 | ||
|
|
234f81ea7c | ||
|
|
743c567bf8 | ||
|
|
21220f1499 | ||
|
|
39f3ffe8fc | ||
|
|
fdb01928a0 | ||
|
|
fbefbc5910 | ||
|
|
9db00d143d | ||
|
|
1cc40d59d8 | ||
|
|
ba1ace56f0 | ||
|
|
6d911e9d6e | ||
|
|
7e2b291122 | ||
|
|
64096f5d08 | ||
|
|
aec1c74fab | ||
|
|
d4a0d77cb9 | ||
|
|
7df209ed3f | ||
|
|
b7421e4499 | ||
|
|
e044fe3601 | ||
|
|
a67801d5ed | ||
|
|
3d37a3a5ae | ||
|
|
bfae5dc828 | ||
|
|
fde5f52a11 | ||
|
|
01bd1bfdca | ||
|
|
ae13900676 | ||
|
|
a65c91874b | ||
|
|
6a3fda069c | ||
|
|
4d34099142 | ||
|
|
e639574973 | ||
|
|
636f9ea873 | ||
|
|
9099409915 | ||
|
|
976f483a99 | ||
|
|
8a32b7a56b | ||
|
|
7eeb06b406 | ||
|
|
4540a1c656 | ||
|
|
acc7b32b24 | ||
|
|
b92d513f5d | ||
|
|
3458fccd4e | ||
|
|
fdfb00368c | ||
|
|
6bd369cfb2 | ||
|
|
0df26c6214 | ||
|
|
6e22da1d97 | ||
|
|
1725809335 | ||
|
|
3eff49feda | ||
|
|
5caedebb06 | ||
|
|
d823028b72 | ||
|
|
49b2d6e22a | ||
|
|
4236d57e96 | ||
|
|
36d463a1dc | ||
|
|
adbafd6bab | ||
|
|
b5ebe1259c | ||
|
|
e0ae0f481b | ||
|
|
8037c79fc0 | ||
|
|
892d303de1 | ||
|
|
ff3979263e | ||
|
|
01afefd3d4 | ||
|
|
830627e7bc | ||
|
|
601d726fcc | ||
|
|
0be1ca0199 | ||
|
|
8602593149 | ||
|
|
9bca601ce6 | ||
|
|
c73acd59d4 | ||
|
|
3c334e8882 | ||
|
|
5f811cadf8 | ||
|
|
4252475de0 | ||
|
|
45f1822681 | ||
|
|
498a23d907 | ||
|
|
5dff8a18f4 | ||
|
|
676c02626d | ||
|
|
64950d0a99 | ||
|
|
16f74cb85c | ||
|
|
aadc961429 | ||
|
|
be66fd92f4 | ||
|
|
feea349f25 | ||
|
|
1c04c3a257 | ||
|
|
818d3f645f | ||
|
|
15d6bb1f14 | ||
|
|
211dbd123f | ||
|
|
c67364fe76 | ||
|
|
2043983f69 | ||
|
|
2f408936a0 | ||
|
|
02c1d0f267 | ||
|
|
4728c346cc | ||
|
|
9588dd8a0e | ||
|
|
e3e48aed1b | ||
|
|
e60f9cf6c5 | ||
|
|
c7e309ccb1 | ||
|
|
ad8aafbd07 | ||
|
|
9e671c3dee | ||
|
|
d97abe36f2 | ||
|
|
faa8946056 | ||
|
|
8b2b1f0bfc | ||
|
|
16678cd5a0 | ||
|
|
0bd6a199a3 | ||
|
|
58e2b7e1b8 |
1
.bazelversion
Normal file
1
.bazelversion
Normal file
@@ -0,0 +1 @@
|
||||
5.0.0
|
||||
83
.github/workflows/ci.yml
vendored
83
.github/workflows/ci.yml
vendored
@@ -1,62 +1,20 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- 'Source/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'Source/**'
|
||||
|
||||
jobs:
|
||||
preqs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
|
||||
build_driver: ${{ steps.step1.outputs.build_driver }}
|
||||
|
||||
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_driver=0
|
||||
build_and_run_tests=0
|
||||
|
||||
for file in `cat files.txt`; do
|
||||
if [[ $file = Source/* ]]; then
|
||||
build_and_run_test=1;
|
||||
if [[ $file = Source/santa_driver/* || $file = Source/common/* ]]; then
|
||||
build_driver=1;
|
||||
break;
|
||||
fi
|
||||
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
|
||||
|
||||
if [[ $build_driver != 0 ]]; then
|
||||
echo "NEED TO BUILD DRIVER"
|
||||
echo "::set-output name=build_driver::true"
|
||||
else
|
||||
echo "::set-output name=build_driver::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
|
||||
@@ -66,45 +24,26 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11]
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
|
||||
|
||||
build_driver:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.build_driver == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Driver
|
||||
run: bazel build --apple_generate_dsym -c opt :release_driver --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
|
||||
@@ -113,13 +52,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
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
|
||||
|
||||
80
BUILD
80
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,8 +10,14 @@ exports_files(["LICENSE"])
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_version = SANTA_VERSION,
|
||||
short_version_string = SANTA_VERSION,
|
||||
build_label_pattern = ".*santa_{release}\\.{build}",
|
||||
build_version = "{release}.{build}",
|
||||
capture_groups = {
|
||||
"release": "\\d{4}\\.\\d+",
|
||||
"build": "\\d+",
|
||||
},
|
||||
fallback_build_label = "santa_9999.1.1",
|
||||
short_version_string = "{release}",
|
||||
)
|
||||
|
||||
# Used to detect release builds
|
||||
@@ -22,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"],
|
||||
)
|
||||
|
||||
@@ -49,7 +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 kextunload -b com.google.santa-driver 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
|
||||
""",
|
||||
)
|
||||
@@ -60,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
|
||||
""",
|
||||
)
|
||||
@@ -68,14 +75,11 @@ run_command(
|
||||
name = "reload",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/santa_driver",
|
||||
],
|
||||
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_driver/santa_driver.zip >/dev/null
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa/Santa.zip >/dev/null
|
||||
echo "You may be asked for your password for sudo"
|
||||
@@ -97,6 +101,7 @@ genrule(
|
||||
"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",
|
||||
@@ -106,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."
|
||||
@@ -148,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
|
||||
@@ -178,55 +187,6 @@ genrule(
|
||||
heuristic_label_expansion = 0,
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "release_driver",
|
||||
srcs = [
|
||||
"//Source/santa_driver",
|
||||
],
|
||||
outs = ["santa-driver-" + SANTA_VERSION + ".tar.gz"],
|
||||
cmd = select({
|
||||
"//conditions:default": """
|
||||
echo "ERROR: Trying to create a release tarball without optimization."
|
||||
echo "Please add '-c opt' flag to bazel invocation"
|
||||
""",
|
||||
":opt_build": """
|
||||
# Extract santa_driver.zip
|
||||
for SRC in $(SRCS); do
|
||||
if [ "$$(basename $${SRC})" == "santa_driver.zip" ]; then
|
||||
mkdir -p $(@D)/binaries
|
||||
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# Gather together the dSYMs. Throw an error if no dSYMs were found
|
||||
for SRC in $(SRCS); do
|
||||
case $${SRC} in
|
||||
*santa-driver.kext.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santa-driver.kext.dSYM
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Cause a build failure if the dSYMs are missing.
|
||||
if [[ ! -d "$(@D)/dsym" ]]; then
|
||||
echo "dsym dir missing: Did you forget to use --apple_generate_dsym?"
|
||||
echo "This flag is required for the 'release' target."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update all the timestamps to now. Bazel avoids timestamps to allow
|
||||
# builds to be hermetic and cacheable but for releases we want the
|
||||
# timestamps to be more-or-less correct.
|
||||
find $(@D)/{binaries,dsym} -exec touch {} \\;
|
||||
|
||||
# Create final output tar
|
||||
tar -C $(@D) -czpf $(@) binaries dsym
|
||||
""",
|
||||
}),
|
||||
heuristic_label_expansion = 0,
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>Santa</title>
|
||||
<options customize="never" allow-external-scripts="no"/>
|
||||
<options customize="never" allow-external-scripts="no" hostArchitectures="x86_64,arm64" />
|
||||
|
||||
<choices-outline>
|
||||
<line choice="default" />
|
||||
@@ -9,10 +9,8 @@
|
||||
|
||||
<choice id="default">
|
||||
<pkg-ref id="com.google.santa"/>
|
||||
<pkg-ref id="com.google.santa-driver"/>
|
||||
</choice>
|
||||
|
||||
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
|
||||
<pkg-ref id="com.google.santa-driver" active="system.compareVersions(my.target.systemVersion.ProductVersion, '10.15') < 0">kext.pkg</pkg-ref>
|
||||
|
||||
</installer-gui-script>
|
||||
|
||||
@@ -44,29 +44,28 @@ function die {
|
||||
}
|
||||
|
||||
readonly INPUT_APP="${RELEASE_ROOT}/binaries/Santa.app"
|
||||
readonly INPUT_KEXT="${RELEASE_ROOT}/binaries/santa-driver.kext"
|
||||
readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension"
|
||||
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
|
||||
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
|
||||
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
|
||||
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
|
||||
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleVersion)"
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
|
||||
|
||||
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
|
||||
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
|
||||
readonly KEXT_PKG_ROOT="${SCRATCH}/kext_pkg_root"
|
||||
readonly APP_PKG_SCRIPTS="${SCRATCH}/pkg_scripts"
|
||||
readonly ENTITLEMENTS="${SCRATCH}/entitlements"
|
||||
|
||||
readonly SCRIPT_PATH="$(/usr/bin/dirname -- ${BASH_SOURCE[0]})"
|
||||
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${KEXT_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
|
||||
|
||||
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
|
||||
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; 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"
|
||||
|
||||
@@ -85,7 +84,7 @@ for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INP
|
||||
done
|
||||
|
||||
# Notarize all the bundles
|
||||
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
|
||||
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "zipping ${BN}"
|
||||
@@ -96,8 +95,8 @@ for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
|
||||
done
|
||||
|
||||
# Staple the App and Kext.
|
||||
for ARTIFACT in "${INPUT_APP}" "${INPUT_KEXT}"; do
|
||||
# Staple the App.
|
||||
for ARTIFACT in "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "stapling ${BN}"
|
||||
@@ -115,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" \
|
||||
@@ -132,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}/"
|
||||
@@ -153,22 +153,6 @@ echo "creating app pkg"
|
||||
--scripts "${APP_PKG_SCRIPTS}" \
|
||||
"${SCRATCH}/app.pkg"
|
||||
|
||||
echo "creating kext pkg"
|
||||
/bin/mkdir -p "${KEXT_PKG_ROOT}/Library/Extensions"
|
||||
/bin/cp -vXR "${RELEASE_ROOT}/binaries/santa-driver.kext" "${KEXT_PKG_ROOT}/Library/Extensions/"
|
||||
/usr/bin/pkgbuild --analyze --root "${KEXT_PKG_ROOT}" "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsRelocatable -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsVersionChecked -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleOverwriteAction -string upgrade "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace ChildBundles -json "[]" "${SCRATCH}/component.plist"
|
||||
|
||||
# Build kext package
|
||||
/usr/bin/pkgbuild --identifier "com.google.santa-driver" \
|
||||
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
|
||||
--root "${KEXT_PKG_ROOT}" \
|
||||
--component-plist "${SCRATCH}/component.plist" \
|
||||
"${SCRATCH}/kext.pkg"
|
||||
|
||||
# Build signed distribution package
|
||||
echo "productbuild pkg"
|
||||
/bin/mkdir -p "${SCRATCH}/${RELEASE_NAME}"
|
||||
|
||||
@@ -14,7 +14,6 @@ mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
# Remove the kext before com.google.santa.daemon loads if the SystemExtension is already present.
|
||||
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && rm -rf /Library/Extensions/santa-driver.kext
|
||||
|
||||
# Load com.google.santa.daemon, its main has logic to handle loading the kext
|
||||
@@ -27,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,6 +27,9 @@ fi
|
||||
# Unload metric service
|
||||
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
|
||||
|
||||
# Unload sync service
|
||||
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
@@ -53,16 +56,13 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
/bin/cp -r ${BINARIES}/Santa.app /Applications
|
||||
|
||||
# Only copy the kext if the SystemExtension is not present.
|
||||
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 || /bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions && /usr/sbin/kextcache -update-volume / -bundle-id com.google.santa-driver
|
||||
|
||||
/bin/mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
|
||||
|
||||
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
|
||||
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
@@ -78,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
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && /Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
# remove helper XPC services
|
||||
/bin/launchctl remove com.google.santa.bundleservice
|
||||
/bin/launchctl remove com.google.santa.metricservice
|
||||
/bin/launchctl remove com.google.santa.syncservice
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
@@ -25,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
|
||||
|
||||
15
README.md
15
README.md
@@ -1,15 +1,14 @@
|
||||
# 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 or
|
||||
kernel extension (depending on the macOS version) that monitors for executions,
|
||||
a daemon that makes execution decisions based on the contents of a local
|
||||
database, a GUI agent that notifies the user in case of a block decision
|
||||
and a command-line utility for managing the system and synchronizing the
|
||||
database with a server.
|
||||
Santa is a binary authorization system for macOS. It consists of a system
|
||||
extension that monitors for executions, a daemon that makes execution decisions
|
||||
based on the contents of a local database, a GUI agent that notifies the user in
|
||||
case of a block decision and a command-line utility for managing the system and
|
||||
synchronizing the database with a server.
|
||||
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
@@ -17,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,13 +1,32 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
proto_library(
|
||||
name = "santa_proto",
|
||||
srcs = ["santa.proto"],
|
||||
deps = [
|
||||
"@com_google_protobuf//:any_proto",
|
||||
"@com_google_protobuf//:timestamp_proto",
|
||||
],
|
||||
)
|
||||
|
||||
objc_proto_library(
|
||||
name = "santa_objc_proto",
|
||||
copts = ["-fno-objc-arc"],
|
||||
non_arc_srcs = ["Santa.pbobjc.m"],
|
||||
protos = [":santa_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
@@ -16,7 +35,7 @@ santa_unit_test(
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -27,6 +46,7 @@ objc_library(
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -37,8 +57,10 @@ objc_library(
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTDeviceEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -47,11 +69,26 @@ objc_library(
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTAllowlistInfo",
|
||||
srcs = ["SNTAllowlistInfo.m"],
|
||||
hdrs = ["SNTAllowlistInfo.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
@@ -63,6 +100,7 @@ objc_library(
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTStrengthify",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
@@ -85,28 +123,14 @@ objc_library(
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTKernelCommon",
|
||||
hdrs = ["SNTKernelCommon.h"],
|
||||
name = "SNTCommon",
|
||||
hdrs = ["SNTCommon.h"],
|
||||
defines = [
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTLoggingKernel",
|
||||
hdrs = ["SNTLogging.h"],
|
||||
copts = [
|
||||
"-mkernel",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = [
|
||||
"KERNEL",
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
@@ -122,28 +146,20 @@ cc_library(
|
||||
deps = [":SNTLogging"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTPrefixTreeKernel",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = [
|
||||
"-std=c++11",
|
||||
"-mkernel",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = [
|
||||
"KERNEL",
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
deps = [":SNTLoggingKernel"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
deps = [":SNTCommonEnums"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSyncConstants",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTRuleTest",
|
||||
srcs = ["SNTRuleTest.m"],
|
||||
deps = [":SNTRule"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -161,6 +177,12 @@ cc_library(
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSyncConstants",
|
||||
srcs = ["SNTSyncConstants.m"],
|
||||
hdrs = ["SNTSyncConstants.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSystemInfo",
|
||||
srcs = ["SNTSystemInfo.m"],
|
||||
@@ -191,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",
|
||||
],
|
||||
)
|
||||
@@ -216,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",
|
||||
],
|
||||
@@ -241,8 +261,8 @@ objc_library(
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
|
||||
32
Source/common/SNTAllowlistInfo.h
Normal file
32
Source/common/SNTAllowlistInfo.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// Store information about new allowlist rules for later logging.
|
||||
///
|
||||
@interface SNTAllowlistInfo : NSObject
|
||||
|
||||
@property pid_t pid;
|
||||
@property int pidversion;
|
||||
@property NSString *targetPath;
|
||||
@property NSString *sha256;
|
||||
|
||||
- (instancetype)initWithPid:(pid_t)pid
|
||||
pidversion:(int)pidver
|
||||
targetPath:(NSString*)targetPath
|
||||
sha256:(NSString*)hash;
|
||||
|
||||
@end
|
||||
32
Source/common/SNTAllowlistInfo.m
Normal file
32
Source/common/SNTAllowlistInfo.m
Normal file
@@ -0,0 +1,32 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTAllowlistInfo.h"
|
||||
|
||||
@implementation SNTAllowlistInfo
|
||||
|
||||
- (instancetype)initWithPid:(pid_t)pid
|
||||
pidversion:(int)pidver
|
||||
targetPath:(NSString *)targetPath
|
||||
sha256:(NSString *)hash {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_pid = pid;
|
||||
_pidversion = pidver;
|
||||
_targetPath = targetPath;
|
||||
_sha256 = hash;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -24,12 +24,17 @@
|
||||
|
||||
///
|
||||
/// Return a message suitable for presenting to the user.
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one.
|
||||
///
|
||||
/// In SantaGUI this will return an NSAttributedString with links and formatting included
|
||||
/// while for santad all HTML will be properly stripped.
|
||||
///
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message;
|
||||
|
||||
///
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one, formatted using
|
||||
/// +[SNTBlockMessage formatMessage].
|
||||
///
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage;
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message {
|
||||
NSString *htmlHeader =
|
||||
@"<html><head><style>"
|
||||
@"body {"
|
||||
@@ -48,6 +47,22 @@
|
||||
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *message;
|
||||
if (customMessage.length) {
|
||||
message = customMessage;
|
||||
@@ -64,19 +79,7 @@
|
||||
@"because it has been deemed malicious.";
|
||||
}
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
return [SNTBlockMessage formatMessage:message];
|
||||
}
|
||||
|
||||
+ (NSString *)stringFromHTML:(NSString *)html {
|
||||
@@ -116,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%"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
|
||||
@class MOLCertificate;
|
||||
|
||||
|
||||
@@ -13,50 +13,19 @@
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Common defines between kernel <-> userspace
|
||||
/// Common defines between daemon <-> client
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__KERNELCOMMON_H
|
||||
#define SANTA__COMMON__KERNELCOMMON_H
|
||||
#ifndef SANTA__COMMON__COMMON_H
|
||||
#define SANTA__COMMON__COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
// Defines the name of the userclient class and the driver bundle ID.
|
||||
#define USERCLIENT_CLASS "com_google_SantaDriver"
|
||||
#define USERCLIENT_ID "com.google.santa-driver"
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
kSantaUserClientAllowBinary,
|
||||
kSantaUserClientAllowCompiler,
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientAcknowledgeBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientRemoveCacheEntry,
|
||||
kSantaUserClientCacheCount,
|
||||
kSantaUserClientCheckCache,
|
||||
kSantaUserClientCacheBucketCount,
|
||||
kSantaUserClientFilemodPrefixFilterAdd,
|
||||
kSantaUserClientFilemodPrefixFilterReset,
|
||||
|
||||
// Any methods supported by the driver should be added above this line to
|
||||
// ensure this remains the count of methods.
|
||||
kSantaUserClientNMethods,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
QUEUETYPE_DECISION,
|
||||
QUEUETYPE_LOG,
|
||||
} santa_queuetype_t;
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
// response methods.
|
||||
typedef enum {
|
||||
ACTION_UNSET = 0,
|
||||
|
||||
@@ -103,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;
|
||||
@@ -122,25 +85,18 @@ 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;
|
||||
|
||||
// Used for the kSantaUserClientCacheBucketCount request.
|
||||
typedef struct {
|
||||
uint16_t per_bucket[1024];
|
||||
uint64_t start;
|
||||
} santa_bucket_count_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
#endif // SANTA__COMMON__COMMON_H
|
||||
@@ -92,6 +92,23 @@ typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
|
||||
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) {
|
||||
@@ -100,7 +117,6 @@ typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
SNTMetricFormatTypeMonarchJSON,
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath =
|
||||
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
|
||||
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
|
||||
///
|
||||
/// Singleton that provides an interface for managing configuration values on disk
|
||||
/// @note This class is designed as a singleton but that is not strictly enforced.
|
||||
@@ -26,7 +28,7 @@
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// The operating mode.
|
||||
/// The operating mode. Defaults to MONITOR.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTClientMode clientMode;
|
||||
|
||||
@@ -35,6 +37,43 @@
|
||||
///
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
|
||||
|
||||
///
|
||||
/// Enable Fail Close mode. Defaults to NO.
|
||||
/// This controls Santa's behavior when a failure occurs, such as an
|
||||
/// inability to read a file. By default, to prevent bugs or misconfiguration
|
||||
/// from rendering a machine inoperable Santa will fail open and allow
|
||||
/// execution. With this setting enabled, Santa will fail closed if the client
|
||||
/// is in LOCKDOWN mode, offering a higher level of security but with a higher
|
||||
/// potential for causing problems.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL failClosed;
|
||||
|
||||
///
|
||||
/// A set of static rules that should always apply. These can be used as a
|
||||
/// fallback set of rules for management tools that should always be allowed to
|
||||
/// run even if a sync server does something unexpected. It can also be used
|
||||
/// as the sole source of rules, distributed with an MDM.
|
||||
///
|
||||
/// The value of this key should be an array containing dictionaries. Each
|
||||
/// dictionary should contain the same keys used for syncing, e.g:
|
||||
///
|
||||
/// <key>StaticRules</key>
|
||||
/// <array>
|
||||
/// <dict>
|
||||
/// <key>identifier</key>
|
||||
/// <string>binary sha256, certificate sha256, team ID</string>
|
||||
/// <key>rule_type</key>
|
||||
/// <string>BINARY</string> (one of BINARY, CERTIFICATE or TEAMID)
|
||||
/// <key>policy</key>
|
||||
/// <string>BLOCKLIST</string> (one of ALLOWLIST, ALLOWLIST_COMPILER, BLOCKLIST, SILENT_BLOCKLIST)
|
||||
/// </dict>
|
||||
/// </array>
|
||||
///
|
||||
/// The return of this property is a dictionary where the keys are the
|
||||
/// identifiers of each rule, with the SNTRule as a value
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary<NSString *, SNTRule *> *staticRules;
|
||||
|
||||
///
|
||||
/// The regex of allowed paths. Regexes are specified in ICU format.
|
||||
///
|
||||
@@ -140,8 +179,13 @@
|
||||
|
||||
///
|
||||
/// Defines how event logs are stored. Options are:
|
||||
/// SNTEventLogTypeSyslog: Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog: Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// 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.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
@@ -157,6 +201,42 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectory will provide the base path used for
|
||||
/// saving logs using the maildir format.
|
||||
/// Defaults to /var/db/santa/mail.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *mailDirectory;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the mailDirectory.
|
||||
/// Defaults to 100.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectoryFileSizeThresholdKB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the mailDirectory.
|
||||
/// Defaults to 500.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// of time an event will be stored in memory before being written to disk.
|
||||
/// Defaults to 5.0.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) float mailDirectoryEventMaxFlushTimeSec;
|
||||
|
||||
///
|
||||
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
|
||||
/// has been overriden, this is the host's UUID.
|
||||
@@ -164,16 +244,6 @@
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
|
||||
///
|
||||
/// Use the bundled SystemExtension on macOS 10.15+, defaults to YES.
|
||||
/// Disable to continue using the bundled KEXT.
|
||||
/// This is a one way switch, if this is ever true on macOS 10.15+ the KEXT will be deleted.
|
||||
/// This gives admins control over the timing of switching to the SystemExtension. The intended use
|
||||
/// case is to have an MDM deliver the requisite SystemExtension and TCC profiles before attempting
|
||||
/// to load.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSystemExtension;
|
||||
|
||||
///
|
||||
/// Use an internal cache for decisions instead of relying on the caching
|
||||
/// mechanism built-in to the EndpointSecurity framework. This may increase
|
||||
@@ -235,6 +305,20 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is denied
|
||||
/// from the BlockUSB configuration setting. If not configured, a reasonable
|
||||
/// default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is forcibly
|
||||
/// remounted to a different set of permissions from the BlockUSB and RemountUSBMode
|
||||
/// configuration settings. If not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *remountUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// The notification text to display when the client goes into MONITOR mode.
|
||||
/// Defaults to "Switching into Monitor mode"
|
||||
@@ -356,6 +440,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.
|
||||
///
|
||||
@@ -427,6 +527,11 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportInterval;
|
||||
|
||||
///
|
||||
/// Duration in seconds for metrics export timeout. Defaults to 30;
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportTimeout;
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
///
|
||||
|
||||
@@ -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";
|
||||
@@ -66,6 +72,9 @@ static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
|
||||
static NSString *const kRemountUSBBlockMessage = @"RemountUSBBlockMessage";
|
||||
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
@@ -77,10 +86,13 @@ static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters
|
||||
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
static NSString *const kEventLogPath = @"EventLogPath";
|
||||
static NSString *const kMailDirectory = @"MailDirectory";
|
||||
static NSString *const kMailDirectoryFileSizeThresholdKB = @"MailDirectoryFileSizeThresholdKB";
|
||||
static NSString *const kMailDirectorySizeThresholdMB = @"MailDirectorySizeThresholdMB";
|
||||
static NSString *const kMailDirectoryEventMaxFlushTimeSec = @"MailDirectoryEventMaxFlushTimeSec";
|
||||
|
||||
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
|
||||
|
||||
static NSString *const kEnableSystemExtension = @"EnableSystemExtension";
|
||||
static NSString *const kEnableSysxCache = @"EnableSysxCache";
|
||||
|
||||
static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
|
||||
@@ -96,6 +108,7 @@ static NSString *const kFCMAPIKey = @"FCMAPIKey";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kFailClosedKey = @"FailClosed";
|
||||
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
|
||||
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
|
||||
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
|
||||
@@ -104,11 +117,14 @@ static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
|
||||
static NSString *const kAllowedPathRegexKeyDeprecated = @"WhitelistRegex";
|
||||
static NSString *const kBlockedPathRegexKey = @"BlockedPathRegex";
|
||||
static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
|
||||
static NSString *const kEnableAllEventUploadKey = @"EnableAllEventUpload";
|
||||
static NSString *const kDisableUnknownEventUploadKey = @"DisableUnknownEventUpload";
|
||||
|
||||
// TODO(markowsky): move these to sync server only.
|
||||
static NSString *const kMetricFormat = @"MetricFormat";
|
||||
static NSString *const kMetricURL = @"MetricURL";
|
||||
static NSString *const kMetricExportInterval = @"MetricExportInterval";
|
||||
static NSString *const kMetricExportTimeout = @"MetricExportTimeout";
|
||||
static NSString *const kMetricExtraLabels = @"MetricExtraLabels";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
@@ -138,10 +154,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kRemountUSBModeKey : array,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
kSyncCleanRequired : number,
|
||||
kEnableAllEventUploadKey : number,
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kFailClosedKey : number,
|
||||
kEnableTransitiveRulesKey : number,
|
||||
kEnableTransitiveRulesKeyDeprecated : number,
|
||||
kFileChangesRegexKey : re,
|
||||
@@ -160,8 +178,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kEventDetailTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kBannedUSBBlockMessage : string,
|
||||
kRemountUSBBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
@@ -178,8 +199,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
kMailDirectory : string,
|
||||
kMailDirectoryFileSizeThresholdKB : number,
|
||||
kMailDirectorySizeThresholdMB : number,
|
||||
kMailDirectoryEventMaxFlushTimeSec : number,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSystemExtension : number,
|
||||
kEnableSysxCache : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
@@ -191,11 +215,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMetricFormat : string,
|
||||
kMetricURL : string,
|
||||
kMetricExportInterval : number,
|
||||
kMetricExportTimeout : number,
|
||||
kMetricExtraLabels : dictionary,
|
||||
kEnableAllEventUploadKey : number,
|
||||
kDisableUnknownEventUploadKey : number,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
_configState = [self readForcedConfig];
|
||||
[self cacheStaticRules];
|
||||
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
|
||||
_debugFlag = [[NSProcessInfo processInfo].arguments containsObject:@"--debug"];
|
||||
[self startWatchingDefaults];
|
||||
@@ -263,6 +291,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -355,6 +387,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectory {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryFileSizeThresholdKB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectorySizeThresholdMB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryEventMaxFlushTimeSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -363,8 +411,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
|
||||
return [self configStateSet];
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingDisableUnknownEventUpload {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
|
||||
@@ -407,6 +459,26 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlockUSBMount {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBannedUSBBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBMode {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRemountUSBBlockMessage {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUsbBlockMessage {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
@@ -429,6 +501,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)failClosed {
|
||||
NSNumber *n = self.configState[kFailClosedKey];
|
||||
if (n) return [n boolValue];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableTransitiveRules {
|
||||
NSNumber *n = self.syncState[kEnableTransitiveRulesKey];
|
||||
if (n) return [n boolValue];
|
||||
@@ -508,6 +586,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:@"/"];
|
||||
@@ -553,6 +635,21 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return self.configState[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)bannedUSBBlockMessage {
|
||||
if (!self.configState[kBannedUSBBlockMessage]) {
|
||||
return @"The following device has been blocked from mounting.";
|
||||
}
|
||||
|
||||
return self.configState[kBannedUSBBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)remountUSBBlockMessage {
|
||||
if (!self.configState[kRemountUSBBlockMessage]) {
|
||||
return @"The following device has been remounted with reduced permissions.";
|
||||
}
|
||||
return self.configState[kRemountUSBBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configState[kModeNotificationMonitor];
|
||||
}
|
||||
@@ -640,35 +737,83 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (SNTEventLogType)eventLogType {
|
||||
NSString *s = [self.configState[kEventLogType] lowercaseString];
|
||||
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
|
||||
NSString *logType = [self.configState[kEventLogType] lowercaseString];
|
||||
if ([logType isEqualToString:@"protobuf"]) {
|
||||
return SNTEventLogTypeProtobuf;
|
||||
} else if ([logType isEqualToString:@"syslog"]) {
|
||||
return SNTEventLogTypeSyslog;
|
||||
} else if ([logType isEqualToString:@"null"]) {
|
||||
return SNTEventLogTypeNull;
|
||||
} else if ([logType isEqualToString:@"file"]) {
|
||||
return SNTEventLogTypeFilelog;
|
||||
} else {
|
||||
return SNTEventLogTypeFilelog;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
- (NSString *)mailDirectory {
|
||||
return self.configState[kMailDirectory] ?: @"/var/db/santa/mail";
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectoryFileSizeThresholdKB {
|
||||
return self.configState[kMailDirectoryFileSizeThresholdKB]
|
||||
? [self.configState[kMailDirectoryFileSizeThresholdKB] unsignedIntegerValue]
|
||||
: 100;
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectorySizeThresholdMB {
|
||||
return self.configState[kMailDirectorySizeThresholdMB]
|
||||
? [self.configState[kMailDirectorySizeThresholdMB] unsignedIntegerValue]
|
||||
: 500;
|
||||
}
|
||||
|
||||
- (float)mailDirMaxFlushTime {
|
||||
return self.configState[kMailDirectoryEventMaxFlushTimeSec]
|
||||
? [self.configState[kMailDirectoryEventMaxFlushTimeSec] floatValue]
|
||||
: 5.0;
|
||||
}
|
||||
|
||||
- (BOOL)enableMachineIDDecoration {
|
||||
NSNumber *number = self.configState[kEnableMachineIDDecoration];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableSystemExtension {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:@"/Library/Extensions/santa-driver.kext"]) return YES;
|
||||
NSNumber *number = self.configState[kEnableSystemExtension];
|
||||
return number ? [number boolValue] : YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)enableSysxCache {
|
||||
NSNumber *number = self.configState[kEnableSysxCache];
|
||||
return number ? [number boolValue] : YES;
|
||||
}
|
||||
|
||||
- (BOOL)enableCleanSyncEventUpload {
|
||||
NSNumber *number = self.configState[kSyncEnableCleanSyncEventUpload];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableAllEventUpload {
|
||||
NSNumber *n = self.syncState[kEnableAllEventUploadKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kEnableAllEventUploadKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setEnableAllEventUpload:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kEnableAllEventUploadKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (BOOL)disableUnknownEventUpload {
|
||||
NSNumber *n = self.syncState[kDisableUnknownEventUploadKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kDisableUnknownEventUploadKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setDisableUnknownEventUpload:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kDisableUnknownEventUploadKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (BOOL)enableForkAndExitLogging {
|
||||
NSNumber *number = self.configState[kEnableForkAndExitLogging];
|
||||
return number ? [number boolValue] : NO;
|
||||
@@ -751,6 +896,16 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [configuredInterval unsignedIntegerValue];
|
||||
}
|
||||
|
||||
// Returns a default value of 30 (for 30 seconds).
|
||||
- (NSUInteger)metricExportTimeout {
|
||||
NSNumber *configuredInterval = self.configState[kMetricExportTimeout];
|
||||
|
||||
if (configuredInterval == nil) {
|
||||
return 30;
|
||||
}
|
||||
return [configuredInterval unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (NSDictionary *)extraMetricLabels {
|
||||
return self.configState[kMetricExtraLabels];
|
||||
}
|
||||
@@ -861,6 +1016,24 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
///
|
||||
- (void)handleChange {
|
||||
self.configState = [self readForcedConfig];
|
||||
[self cacheStaticRules];
|
||||
}
|
||||
|
||||
///
|
||||
/// Processes the StaticRules key to create SNTRule objects and caches them for quick use
|
||||
///
|
||||
- (void)cacheStaticRules {
|
||||
NSArray *staticRules = self.configState[kStaticRules];
|
||||
if (![staticRules isKindOfClass:[NSArray class]]) return;
|
||||
|
||||
NSMutableDictionary<NSString *, SNTRule *> *rules =
|
||||
[NSMutableDictionary dictionaryWithCapacity:staticRules.count];
|
||||
for (id rule in staticRules) {
|
||||
if (![rule isKindOfClass:[NSDictionary class]]) return;
|
||||
SNTRule *r = [[SNTRule alloc] initWithDictionary:rule];
|
||||
rules[r.identifier] = r;
|
||||
}
|
||||
self.cachedStaticRules = [rules copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
27
Source/common/SNTDeviceEvent.h
Normal file
27
Source/common/SNTDeviceEvent.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Copyright 2022 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SNTDeviceEvent : NSObject <NSSecureCoding>
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname;
|
||||
|
||||
@property NSString *mntonname;
|
||||
@property NSString *mntfromname;
|
||||
@property NSArray<NSString *> *remountArgs;
|
||||
|
||||
- (NSString *)readableRemountArgs;
|
||||
|
||||
@end
|
||||
63
Source/common/SNTDeviceEvent.m
Normal file
63
Source/common/SNTDeviceEvent.m
Normal file
@@ -0,0 +1,63 @@
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
|
||||
@implementation SNTDeviceEvent
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
|
||||
#define ENCODE(obj, key) \
|
||||
if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = mntonname;
|
||||
_mntfromname = mntfromname;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.mntonname, @"mntonname");
|
||||
ENCODE(self.mntfromname, @"mntfromname");
|
||||
ENCODE(self.remountArgs, @"remountArgs");
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = DECODE(NSString, @"mntonname");
|
||||
_mntfromname = DECODE(NSString, @"mntfromname");
|
||||
_remountArgs = DECODEARRAY(NSString, @"remountArgs");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTDeviceEvent '%@' -> '%@' (with permissions: [%@]",
|
||||
self.mntfromname, self.mntonname,
|
||||
[self.remountArgs componentsJoinedByString:@", "]];
|
||||
}
|
||||
|
||||
- (NSString *)readableRemountArgs {
|
||||
NSMutableArray<NSString *> *readable = [NSMutableArray array];
|
||||
for (NSString *arg in self.remountArgs) {
|
||||
if ([arg isEqualToString:@"rdonly"]) {
|
||||
[readable addObject:@"read-only"];
|
||||
} else if ([arg isEqualToString:@"noexec"]) {
|
||||
[readable addObject:@"block executables"];
|
||||
} else {
|
||||
[readable addObject:arg];
|
||||
}
|
||||
}
|
||||
return [readable componentsJoinedByString:@", "];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -13,27 +13,12 @@
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Logging definitions, for both kernel and user space.
|
||||
/// Logging definitions
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__LOGGING_H
|
||||
#define SANTA__COMMON__LOGGING_H
|
||||
|
||||
#ifdef KERNEL
|
||||
|
||||
#include <IOKit/IOLib.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#else // DEBUG
|
||||
#define LOGD(format, ...)
|
||||
#endif // DEBUG
|
||||
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
|
||||
#else // KERNEL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -64,10 +49,11 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
/// Get the logging level for this process.
|
||||
LogLevel EffectiveLogLevel();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
#endif // SANTA__COMMON__LOGGING_H
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -14,13 +14,6 @@
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
|
||||
#else
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <mutex>
|
||||
@@ -46,25 +39,14 @@
|
||||
|
||||
#define lck_mtx_lock(l) l->lock()
|
||||
#define lck_mtx_unlock(l) l->unlock()
|
||||
#endif // KERNEL
|
||||
|
||||
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
max_nodes_ = max_nodes;
|
||||
|
||||
#ifdef KERNEL
|
||||
spt_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
spt_lock_grp_ =
|
||||
lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
|
||||
spt_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
spt_add_lock_ = lck_mtx_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
#else
|
||||
pthread_rwlock_init(&spt_lock_, nullptr);
|
||||
spt_add_lock_ = new std::mutex;
|
||||
#endif
|
||||
}
|
||||
|
||||
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
@@ -241,32 +223,5 @@ SNTPrefixTree::~SNTPrefixTree() {
|
||||
root_ = nullptr;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
|
||||
#ifdef KERNEL
|
||||
if (spt_lock_) {
|
||||
lck_rw_free(spt_lock_, spt_lock_grp_);
|
||||
spt_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_add_lock_) {
|
||||
lck_mtx_free(spt_add_lock_, spt_lock_grp_);
|
||||
spt_add_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_attr_) {
|
||||
lck_attr_free(spt_lock_attr_);
|
||||
spt_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_) {
|
||||
lck_grp_free(spt_lock_grp_);
|
||||
spt_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_attr_) {
|
||||
lck_grp_attr_free(spt_lock_grp_attr_);
|
||||
spt_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
#else
|
||||
pthread_rwlock_destroy(&spt_lock_);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -18,15 +18,11 @@
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
#else
|
||||
// Support for unit testing.
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <mutex>
|
||||
#endif // KERNEL
|
||||
|
||||
///
|
||||
/// SantaPrefixTree is a simple prefix tree implementation.
|
||||
@@ -88,16 +84,8 @@ class SNTPrefixTree {
|
||||
uint32_t max_nodes_;
|
||||
uint32_t node_count_;
|
||||
|
||||
#ifdef KERNEL
|
||||
lck_grp_t *spt_lock_grp_;
|
||||
lck_grp_attr_t *spt_lock_grp_attr_;
|
||||
lck_attr_t *spt_lock_attr_;
|
||||
lck_rw_t *spt_lock_;
|
||||
lck_mtx_t *spt_add_lock_;
|
||||
#else // KERNEL
|
||||
pthread_rwlock_t spt_lock_;
|
||||
std::mutex *spt_add_lock_;
|
||||
#endif // KERNEL
|
||||
};
|
||||
|
||||
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */
|
||||
|
||||
@@ -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
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");
|
||||
|
||||
@@ -21,6 +21,7 @@ extern NSString *const kHostname;
|
||||
extern NSString *const kSantaVer;
|
||||
extern NSString *const kOSVer;
|
||||
extern NSString *const kOSBuild;
|
||||
extern NSString *const kModelIdentifier;
|
||||
extern NSString *const kPrimaryUser;
|
||||
extern NSString *const kRequestCleanSync;
|
||||
extern NSString *const kBatchSize;
|
||||
@@ -49,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;
|
||||
@@ -61,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;
|
||||
@@ -88,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;
|
||||
@@ -131,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";
|
||||
|
||||
@@ -21,6 +21,7 @@ NSString *const kHostname = @"hostname";
|
||||
NSString *const kSantaVer = @"santa_version";
|
||||
NSString *const kOSVer = @"os_version";
|
||||
NSString *const kOSBuild = @"os_build";
|
||||
NSString *const kModelIdentifier = @"model_identifier";
|
||||
NSString *const kPrimaryUser = @"primary_user";
|
||||
NSString *const kRequestCleanSync = @"request_clean_sync";
|
||||
NSString *const kBatchSize = @"batch_size";
|
||||
@@ -50,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";
|
||||
@@ -62,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";
|
||||
@@ -89,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";
|
||||
@@ -124,5 +130,5 @@ NSString *const kLogSync = @"log_sync";
|
||||
|
||||
const NSUInteger kDefaultEventBatchSize = 50;
|
||||
const NSUInteger kDefaultFullSyncInterval = 600;
|
||||
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
|
||||
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;
|
||||
const NSUInteger kDefaultPushNotificationsFullSyncInterval = 14400;
|
||||
const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline = 600;
|
||||
@@ -49,4 +49,9 @@
|
||||
///
|
||||
+ (NSString *)longHostname;
|
||||
|
||||
///
|
||||
/// @return Model Identifier
|
||||
///
|
||||
+ (NSString *)modelIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
@implementation SNTSystemInfo
|
||||
|
||||
@@ -60,6 +61,13 @@
|
||||
return @(hostname);
|
||||
}
|
||||
|
||||
+ (NSString *)modelIdentifier {
|
||||
char model[32];
|
||||
size_t len = 32;
|
||||
sysctlbyname("hw.model", model, &len, NULL, 0);
|
||||
return @(model);
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
+ (NSDictionary *)_systemVersionDictionary {
|
||||
|
||||
@@ -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,13 +27,16 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
@implementation SNTXPCControlInterface
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
}
|
||||
return kBundleID;
|
||||
#ifdef SANTAADHOC
|
||||
// The mach service for an adhoc signed ES sysx uses the "endpoint-security" prefix instead of
|
||||
// the teamid. In Santa's case it will be endpoint-security.com.google.santa.daemon.xpc.
|
||||
return [NSString stringWithFormat:@"endpoint-security.%@.xpc", kBundleID];
|
||||
#else
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSString *)systemExtensionID {
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
@class SNTDeviceEvent;
|
||||
|
||||
/// Protocol implemented by SantaGUI and utilized by santad
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
@@ -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
|
||||
@@ -16,7 +16,7 @@
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@@ -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;
|
||||
|
||||
@@ -20,28 +20,19 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <IOKit/IOLib.h>
|
||||
#else // KERNEL
|
||||
// Support for unit testing.
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
#define panic(args...) \
|
||||
printf(args); \
|
||||
printf("\n"); \
|
||||
abort()
|
||||
#define IOMallocAligned(sz, alignment) malloc(sz);
|
||||
#define IOFreeAligned(addr, sz) free(addr)
|
||||
#define OSTestAndSet OSAtomicTestAndSet
|
||||
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
|
||||
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
|
||||
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif // KERNEL
|
||||
|
||||
/**
|
||||
A type to specialize to help SantaCache with its hashing.
|
||||
@@ -88,8 +79,7 @@ class SantaCache {
|
||||
(1 << (32 -
|
||||
__builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
|
||||
if (unlikely(bucket_count_ > UINT32_MAX)) bucket_count_ = UINT32_MAX;
|
||||
buckets_ = (struct bucket *)IOMallocAligned(
|
||||
bucket_count_ * sizeof(struct bucket), 2);
|
||||
buckets_ = (struct bucket *)malloc(bucket_count_ * sizeof(struct bucket));
|
||||
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
@@ -98,7 +88,7 @@ class SantaCache {
|
||||
*/
|
||||
~SantaCache() {
|
||||
clear();
|
||||
IOFreeAligned(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
free(buckets_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +163,7 @@ class SantaCache {
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
struct entry *next_entry = entry->next;
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
free(entry);
|
||||
entry = next_entry;
|
||||
}
|
||||
}
|
||||
@@ -284,8 +274,8 @@ class SantaCache {
|
||||
} else {
|
||||
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
|
||||
}
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
OSDecrementAtomic(&count_);
|
||||
free(entry);
|
||||
OSAtomicDecrement64((volatile int64_t *)&count_);
|
||||
}
|
||||
|
||||
unlock(bucket);
|
||||
@@ -318,14 +308,13 @@ class SantaCache {
|
||||
|
||||
// Allocate a new entry, set the key and value, then put this new entry at
|
||||
// the head of this bucket's linked list.
|
||||
struct entry *new_entry =
|
||||
(struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
struct entry *new_entry = (struct entry *)malloc(sizeof(struct entry));
|
||||
bzero(new_entry, sizeof(struct entry));
|
||||
new_entry->key = key;
|
||||
new_entry->value = value;
|
||||
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
|
||||
OSIncrementAtomic(&count_);
|
||||
OSAtomicIncrement64((volatile int64_t *)&count_);
|
||||
|
||||
unlock(bucket);
|
||||
return true;
|
||||
@@ -335,7 +324,7 @@ class SantaCache {
|
||||
Lock a bucket. Spins until the lock is acquired.
|
||||
*/
|
||||
inline void lock(struct bucket *bucket) const {
|
||||
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
||||
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
||||
;
|
||||
}
|
||||
|
||||
@@ -343,7 +332,8 @@ class SantaCache {
|
||||
Unlock a bucket. Panics if the lock wasn't locked.
|
||||
*/
|
||||
inline void unlock(struct bucket *bucket) const {
|
||||
if (unlikely(OSTestAndClear(7, (volatile uint8_t *)&bucket->head))) {
|
||||
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
|
||||
0)) {
|
||||
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
|
||||
}
|
||||
}
|
||||
@@ -375,8 +365,6 @@ class SantaCache {
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef KERNEL
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
|
||||
146
Source/common/santa.proto
Normal file
146
Source/common/santa.proto
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// !!! WARNING !!!
|
||||
// This proto is in beta format and subject to change.
|
||||
//
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option objc_class_prefix = "SNTPB";
|
||||
|
||||
package santa;
|
||||
|
||||
message ProcessInfo {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional int32 ppid = 3;
|
||||
optional int32 uid = 4;
|
||||
optional string user = 5;
|
||||
optional int32 gid = 6;
|
||||
optional string group = 7;
|
||||
}
|
||||
|
||||
message FileModification {
|
||||
enum Action {
|
||||
ACTION_UNKNOWN = 0;
|
||||
ACTION_DELETE = 1;
|
||||
ACTION_EXCHANGE = 2;
|
||||
ACTION_LINK = 3;
|
||||
ACTION_RENAME = 4;
|
||||
ACTION_WRITE = 5;
|
||||
}
|
||||
|
||||
optional Action action = 1;
|
||||
optional string path = 2;
|
||||
optional string newpath = 3;
|
||||
optional string process = 4;
|
||||
optional string process_path = 5;
|
||||
optional ProcessInfo process_info = 6;
|
||||
optional string machine_id = 7;
|
||||
}
|
||||
|
||||
message Execution {
|
||||
enum Decision {
|
||||
DECISION_UNKNOWN = 0;
|
||||
DECISION_ALLOW = 1;
|
||||
DECISION_DENY = 2;
|
||||
}
|
||||
|
||||
enum Reason {
|
||||
REASON_UNKNOWN = 0;
|
||||
REASON_BINARY = 1;
|
||||
REASON_CERT = 2;
|
||||
REASON_COMPILER = 3;
|
||||
REASON_NOT_RUNNING = 4;
|
||||
REASON_PENDING_TRANSITIVE = 5;
|
||||
REASON_SCOPE = 6;
|
||||
REASON_TEAM_ID = 7;
|
||||
REASON_TRANSITIVE = 8;
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
MODE_UNKNOWN = 0;
|
||||
MODE_LOCKDOWN = 1;
|
||||
MODE_MONITOR = 2;
|
||||
}
|
||||
|
||||
optional Decision decision = 1;
|
||||
optional Reason reason = 2;
|
||||
optional string explain = 3;
|
||||
optional string sha256 = 4;
|
||||
optional string cert_sha256 = 5;
|
||||
optional string cert_cn = 6;
|
||||
optional string quarantine_url = 7;
|
||||
optional ProcessInfo process_info = 8;
|
||||
optional Mode mode = 9;
|
||||
optional string path = 10;
|
||||
optional string original_path = 11;
|
||||
repeated string args = 12;
|
||||
optional string machine_id = 13;
|
||||
optional string team_id = 14;
|
||||
}
|
||||
|
||||
message DiskAppeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
optional string fs = 4;
|
||||
optional string model = 5;
|
||||
optional string serial = 6;
|
||||
optional string bus = 7;
|
||||
optional string dmg_path = 8;
|
||||
optional string appearance = 9;
|
||||
}
|
||||
|
||||
message DiskDisappeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
}
|
||||
|
||||
message Bundle {
|
||||
// This is the hash of the file within the bundle that triggered the event
|
||||
optional string sha256 = 1;
|
||||
// This is the hash of the hashes of all executables in the bundle
|
||||
optional string bundle_hash = 2;
|
||||
optional string bundle_name = 3;
|
||||
optional string bundle_id = 4;
|
||||
optional string bundle_path = 5;
|
||||
optional string path = 6;
|
||||
}
|
||||
|
||||
message Fork {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
message Exit {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
message Allowlist {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional string path = 3;
|
||||
optional string sha256 = 4;
|
||||
}
|
||||
|
||||
message SantaMessage {
|
||||
google.protobuf.Timestamp event_time = 1;
|
||||
|
||||
oneof message {
|
||||
FileModification file_modification = 2;
|
||||
Execution execution = 3;
|
||||
DiskAppeared disk_appeared = 4;
|
||||
DiskDisappeared disk_disappeared = 5;
|
||||
Bundle bundle = 6;
|
||||
Fork fork = 7;
|
||||
Exit exit = 8;
|
||||
Allowlist allowlist = 9;
|
||||
}
|
||||
}
|
||||
|
||||
message LogBatch {
|
||||
repeated google.protobuf.Any records = 1;
|
||||
}
|
||||
@@ -2,6 +2,10 @@ load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-256.png",
|
||||
])
|
||||
@@ -15,6 +19,10 @@ objc_library(
|
||||
"SNTAccessibleTextField.m",
|
||||
"SNTAppDelegate.h",
|
||||
"SNTAppDelegate.m",
|
||||
"SNTBinaryMessageWindowController.h",
|
||||
"SNTBinaryMessageWindowController.m",
|
||||
"SNTDeviceMessageWindowController.h",
|
||||
"SNTDeviceMessageWindowController.m",
|
||||
"SNTMessageWindow.h",
|
||||
"SNTMessageWindow.m",
|
||||
"SNTMessageWindowController.h",
|
||||
@@ -25,19 +33,25 @@ objc_library(
|
||||
],
|
||||
data = [
|
||||
"Resources/AboutWindow.xib",
|
||||
"Resources/DeviceMessageWindow.xib",
|
||||
"Resources/MessageWindow.xib",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"IOKit",
|
||||
"SecurityInterface",
|
||||
"SystemExtensions",
|
||||
"UserNotifications",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTBlockMessage_SantaGUI",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTStrengthify",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -49,6 +63,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/**"]),
|
||||
@@ -59,12 +74,16 @@ 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"],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -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="3840" height="1577"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -47,7 +47,7 @@ There are no user-configurable settings.</string>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SRu-Kf-vu5">
|
||||
<rect key="frame" x="130" y="21" width="111" height="32"/>
|
||||
<rect key="frame" x="129" y="21" width="113" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="99" id="JHv-2J-QSe"/>
|
||||
</constraints>
|
||||
@@ -60,7 +60,7 @@ There are no user-configurable settings.</string>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Udo-BY-n7e">
|
||||
<rect key="frame" x="240" y="21" width="111" height="32"/>
|
||||
<rect key="frame" x="239" y="21" width="113" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="99" id="2Xc-ax-2bV"/>
|
||||
</constraints>
|
||||
|
||||
193
Source/santa/Resources/DeviceMessageWindow.xib
Normal file
193
Source/santa/Resources/DeviceMessageWindow.xib
Normal file
@@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SNTDeviceMessageWindowController">
|
||||
<connections>
|
||||
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="515" height="318"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="515" height="318"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<userGuides>
|
||||
<userLayoutGuide location="344" affinity="minX"/>
|
||||
</userGuides>
|
||||
<subviews>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
|
||||
<rect key="frame" x="16" y="290" width="37" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="31" y="210" width="454" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="139" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device Name" id="KgY-X1-ESG">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="115" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device BSD Path" id="adC-be-Beh">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Device Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="139" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device Name" id="E7T-9h-ofr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.mntonname" id="bOu-gv-1Vh"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField toolTip="Device BSD Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="115" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device BSD Path" id="H1b-Ui-CYo">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.mntfromname" id="Sry-KY-HDb"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
|
||||
<rect key="frame" x="217" y="248" width="82" height="40"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
|
||||
<font key="font" metaFont="systemUltraLight" size="34"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="i8e-8n-tZS" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="91" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="a3r-ng-zxJ"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Remount Mode" id="4D0-8L-ihK">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Device BSD Path" verticalHuggingPriority="750" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kmz-be-oWf" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="91" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="cbM-i5-bJ9"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Remount Mode" id="qI0-Na-kxN">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.readableRemountArgs" id="bOu-gv-1Vl"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="202" y="18" width="112" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Ok" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
|
||||
</buttonCell>
|
||||
<accessibility description="Dismiss Dialog"/>
|
||||
<connections>
|
||||
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
|
||||
<constraint firstItem="kmz-be-oWf" firstAttribute="height" secondItem="i8e-8n-tZS" secondAttribute="height" id="6Ow-Sl-6Wc"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
|
||||
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="55" id="Nsl-zf-poH"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
|
||||
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
|
||||
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="22" id="dYg-zP-wh2"/>
|
||||
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
|
||||
<constraint firstItem="kmz-be-oWf" firstAttribute="centerY" secondItem="i8e-8n-tZS" secondAttribute="centerY" id="ilC-5x-LNe"/>
|
||||
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
|
||||
<constraint firstItem="i8e-8n-tZS" firstAttribute="centerY" secondItem="YNz-ka-cBi" secondAttribute="centerY" constant="24" id="uZs-XT-PuZ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="firstBaseline" secondItem="d9e-Wv-Y5H" secondAttribute="baseline" constant="24" id="xZu-AY-wX5"/>
|
||||
<constraint firstItem="i8e-8n-tZS" firstAttribute="top" secondItem="YNz-ka-cBi" secondAttribute="bottom" constant="8" symbolic="YES" id="ylw-3s-9JW"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="261.5" y="246"/>
|
||||
</window>
|
||||
<userDefaultsController representsSharedInstance="YES" id="iXx-cu-WYe"/>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -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="3840" height="1577"/>
|
||||
<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="479"/>
|
||||
<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"/>
|
||||
@@ -41,7 +41,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="43" y="369" width="454" height="17"/>
|
||||
<rect key="frame" x="43" y="354" width="454" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
@@ -56,7 +56,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="297" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="283" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
@@ -70,7 +70,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="272" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="259" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
|
||||
</constraints>
|
||||
@@ -84,7 +84,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Binary Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="297" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="283" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
|
||||
</constraints>
|
||||
@@ -98,7 +98,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
|
||||
<rect key="frame" x="8" y="247" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="235" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -109,7 +109,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="247" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="235" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="Dem-wH-KHm"/>
|
||||
</constraints>
|
||||
@@ -127,7 +127,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
|
||||
<rect key="frame" x="62" y="248" width="15" height="15"/>
|
||||
<rect key="frame" x="62" y="235" width="15" height="15"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
|
||||
@@ -149,7 +149,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
|
||||
<rect key="frame" x="8" y="222" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="212" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -160,7 +160,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
|
||||
<rect key="frame" x="187" y="222" width="219" height="17"/>
|
||||
<rect key="frame" x="187" y="214" width="219" height="13"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
|
||||
</constraints>
|
||||
@@ -177,7 +177,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MhO-U0-MLR" userLabel="Label: Bundle Identifier">
|
||||
<rect key="frame" x="8" y="197" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="191" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bundle Identifier" id="LEe-u0-52o">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -195,7 +195,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
|
||||
<rect key="frame" x="8" y="157" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="156" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -206,7 +206,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="157" width="294" height="17"/>
|
||||
<rect key="frame" x="187" y="156" width="294" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
|
||||
</constraints>
|
||||
@@ -229,7 +229,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
|
||||
<rect key="frame" x="8" y="132" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="132" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -240,17 +240,16 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="154" y="34" width="112" 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"/>
|
||||
@@ -258,7 +257,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="272" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="259" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
|
||||
</constraints>
|
||||
@@ -272,7 +271,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
|
||||
<rect key="frame" x="113" y="80" width="315" height="29"/>
|
||||
<rect key="frame" x="110" y="80" width="319" height="29"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
|
||||
</constraints>
|
||||
@@ -285,18 +284,17 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="278" y="34" width="110" height="23"/>
|
||||
<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>
|
||||
@@ -305,7 +303,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="132" width="294" height="17"/>
|
||||
<rect key="frame" x="187" y="132" width="294" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
@@ -322,14 +320,14 @@ DQ
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<progressIndicator wantsLayer="YES" canDrawConcurrently="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
|
||||
<rect key="frame" x="187" y="199" width="217" height="12"/>
|
||||
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
|
||||
<rect key="frame" x="187" y="193" width="217" height="12"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="217" id="M22-Dv-KIP"/>
|
||||
</constraints>
|
||||
</progressIndicator>
|
||||
<textField toolTip="Bundle SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xP7-jE-NF8">
|
||||
<rect key="frame" x="187" y="197" width="219" height="17"/>
|
||||
<rect key="frame" x="187" y="193" width="219" height="13"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="215" id="s7W-o9-2nN"/>
|
||||
</constraints>
|
||||
@@ -346,7 +344,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LHV-gV-vyf">
|
||||
<rect key="frame" x="187" y="182" width="219" height="17"/>
|
||||
<rect key="frame" x="187" y="180" width="219" height="13"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="215" id="LUu-Vd-peN"/>
|
||||
</constraints>
|
||||
@@ -357,7 +355,7 @@ DQ
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<box horizontalHuggingPriority="750" boxType="custom" borderType="line" title="Line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
|
||||
<rect key="frame" x="168" y="132" width="1" height="207"/>
|
||||
<rect key="frame" x="168" y="132" width="1" height="191"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
@@ -366,7 +364,7 @@ DQ
|
||||
<font key="titleFont" metaFont="system"/>
|
||||
</box>
|
||||
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="322" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="307" width="315" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="Pav-ZA-iAu"/>
|
||||
</constraints>
|
||||
@@ -384,7 +382,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
|
||||
<rect key="frame" x="8" y="322" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="307" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="8mA-zi-Ev7"/>
|
||||
</constraints>
|
||||
@@ -405,7 +403,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
|
||||
<rect key="frame" x="229" y="408" width="82" height="41"/>
|
||||
<rect key="frame" x="229" y="392" width="82" height="40"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
|
||||
<font key="font" metaFont="systemUltraLight" size="34"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
||||
65
Source/santa/SNTBinaryMessageWindowController.h
Normal file
65
Source/santa/SNTBinaryMessageWindowController.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
@interface SNTBinaryMessageWindowController : SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender;
|
||||
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash;
|
||||
|
||||
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashLabel;
|
||||
|
||||
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashTitle;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
|
||||
|
||||
///
|
||||
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
|
||||
/// if it isn't needed or set its title if it is.
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *openEventButton;
|
||||
|
||||
///
|
||||
/// The execution event that this window is for
|
||||
///
|
||||
@property(readonly) SNTStoredEvent *event;
|
||||
|
||||
///
|
||||
/// The root progress object. Child nodes are vended to santad to report on work being done.
|
||||
///
|
||||
@property NSProgress *progress;
|
||||
|
||||
@end
|
||||
176
Source/santa/SNTBinaryMessageWindowController.m
Normal file
176
Source/santa/SNTBinaryMessageWindowController.m
Normal file
@@ -0,0 +1,176 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTBinaryMessageWindowController.h"
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
|
||||
@interface SNTBinaryMessageWindowController ()
|
||||
/// The custom message to display for this event
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
/// A 'friendly' string representing the certificate information
|
||||
@property(readonly, nonatomic) NSString *publisherInfo;
|
||||
|
||||
/// An optional message to display with this block.
|
||||
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
|
||||
|
||||
/// Reference to the "Application Name" label in the XIB. Used to remove if application
|
||||
/// doesn't have a CFBundleName.
|
||||
@property(weak) IBOutlet NSTextField *applicationNameLabel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTBinaryMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = message;
|
||||
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
|
||||
[_progress addObserver:self
|
||||
forKeyPath:@"fractionCompleted"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context {
|
||||
if ([keyPath isEqualToString:@"fractionCompleted"]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSProgress *progress = object;
|
||||
if (progress.fractionCompleted != 0.0) {
|
||||
self.hashingIndicator.indeterminate = NO;
|
||||
}
|
||||
self.hashingIndicator.doubleValue = progress.fractionCompleted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.event.needsBundleHash) {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.hashingIndicator removeFromSuperview];
|
||||
[self.foundFileCountLabel removeFromSuperview];
|
||||
} else {
|
||||
self.openEventButton.enabled = NO;
|
||||
self.hashingIndicator.indeterminate = YES;
|
||||
[self.hashingIndicator startAnimation:self];
|
||||
self.bundleHashLabel.hidden = YES;
|
||||
self.foundFileCountLabel.stringValue = @"";
|
||||
}
|
||||
|
||||
if (!self.event.fileBundleName) {
|
||||
[self.applicationNameLabel removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
return self.event.fileSHA256;
|
||||
}
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender {
|
||||
// SFCertificatePanel expects an NSArray of SecCertificateRef's
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
|
||||
for (MOLCertificate *cert in self.event.signingChain) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
|
||||
modalDelegate:nil
|
||||
didEndSelector:nil
|
||||
contextInfo:nil
|
||||
certificates:certArray
|
||||
showGroup:YES];
|
||||
}
|
||||
|
||||
- (IBAction)openEventDetails:(id)sender {
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
#pragma mark Generated properties
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
||||
if (![key isEqualToString:@"event"]) {
|
||||
return [NSSet setWithObject:@"event"];
|
||||
} else {
|
||||
return [NSSet set];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
MOLCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
|
||||
customMessage:self.customMessage];
|
||||
}
|
||||
|
||||
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
|
||||
// UI updates must happen on the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.event.idx isEqual:event.idx]) {
|
||||
if (bundleHash) {
|
||||
[self.bundleHashLabel setHidden:NO];
|
||||
} else {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.bundleHashTitle removeFromSuperview];
|
||||
}
|
||||
self.event.fileBundleHash = bundleHash;
|
||||
[self.foundFileCountLabel removeFromSuperview];
|
||||
[self.hashingIndicator setHidden:YES];
|
||||
[self.openEventButton setEnabled:YES];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
38
Source/santa/SNTDeviceMessageWindowController.h
Normal file
38
Source/santa/SNTDeviceMessageWindowController.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
@interface SNTDeviceMessageWindowController : SNTMessageWindowController
|
||||
|
||||
@property(weak) IBOutlet NSTextField *remountArgsLabel;
|
||||
@property(weak) IBOutlet NSTextField *remountArgsTitle;
|
||||
|
||||
// The device event this window is for.
|
||||
@property(readonly) SNTDeviceEvent *event;
|
||||
|
||||
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
57
Source/santa/SNTDeviceMessageWindowController.m
Normal file
57
Source/santa/SNTDeviceMessageWindowController.m
Normal file
@@ -0,0 +1,57 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTDeviceMessageWindowController.h"
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SNTDeviceMessageWindowController ()
|
||||
@property(copy, nullable) NSString *customMessage;
|
||||
@end
|
||||
|
||||
@implementation SNTDeviceMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message {
|
||||
self = [super initWithWindowNibName:@"DeviceMessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = message;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
if (!self.event.remountArgs || [self.event.remountArgs count] <= 0) {
|
||||
[self.remountArgsLabel removeFromSuperview];
|
||||
[self.remountArgsTitle removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage formatMessage:self.customMessage];
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
return self.event.mntonname;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,73 +1,21 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@protocol SNTMessageWindowControllerDelegate
|
||||
- (void)windowDidCloseSilenceHash:(NSString *)hash;
|
||||
@end
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
@interface SNTMessageWindowController : NSWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
|
||||
|
||||
- (IBAction)showWindow:(id)sender;
|
||||
- (IBAction)closeWindow:(id)sender;
|
||||
- (IBAction)showCertInfo:(id)sender;
|
||||
|
||||
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashLabel;
|
||||
/// Generate a distinct key for a given displayed event. This key is used for silencing future
|
||||
/// notifications.
|
||||
- (NSString *)messageHash;
|
||||
|
||||
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashTitle;
|
||||
/// Linked to checkbox in UI to prevent future notifications for the given event.
|
||||
@property BOOL silenceFutureNotifications;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
|
||||
|
||||
///
|
||||
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
|
||||
/// if it isn't needed or set its title if it is.
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *openEventButton;
|
||||
|
||||
///
|
||||
/// The execution event that this window is for
|
||||
///
|
||||
@property(readonly) SNTStoredEvent *event;
|
||||
|
||||
///
|
||||
/// The root progress object. Child nodes are vended to santad to report on work being done.
|
||||
///
|
||||
@property NSProgress *progress;
|
||||
|
||||
///
|
||||
/// The delegate to inform when the notification is dismissed
|
||||
///
|
||||
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,117 +1,13 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
|
||||
@interface SNTMessageWindowController ()
|
||||
/// The custom message to display for this event
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
/// A 'friendly' string representing the certificate information
|
||||
@property(readonly, nonatomic) NSString *publisherInfo;
|
||||
|
||||
/// An optional message to display with this block.
|
||||
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
|
||||
|
||||
/// Reference to the "Application Name" label in the XIB. Used to remove if application
|
||||
/// doesn't have a CFBundleName.
|
||||
@property(weak) IBOutlet NSTextField *applicationNameLabel;
|
||||
|
||||
/// Linked to checkbox in UI to prevent future notifications for this binary.
|
||||
@property BOOL silenceFutureNotifications;
|
||||
@end
|
||||
|
||||
@implementation SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = message;
|
||||
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
|
||||
[_progress addObserver:self
|
||||
forKeyPath:@"fractionCompleted"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context {
|
||||
if ([keyPath isEqualToString:@"fractionCompleted"]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSProgress *progress = object;
|
||||
if (progress.fractionCompleted != 0.0) {
|
||||
self.hashingIndicator.indeterminate = NO;
|
||||
}
|
||||
self.hashingIndicator.doubleValue = progress.fractionCompleted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
[self.window setLevel:NSPopUpMenuWindowLevel];
|
||||
[self.window setMovableByWindowBackground:YES];
|
||||
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.event.needsBundleHash) {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.hashingIndicator removeFromSuperview];
|
||||
[self.foundFileCountLabel removeFromSuperview];
|
||||
} else {
|
||||
self.openEventButton.enabled = NO;
|
||||
self.hashingIndicator.indeterminate = YES;
|
||||
[self.hashingIndicator startAnimation:self];
|
||||
self.bundleHashLabel.hidden = YES;
|
||||
self.foundFileCountLabel.stringValue = @"";
|
||||
}
|
||||
|
||||
if (!self.event.fileBundleName) {
|
||||
[self.applicationNameLabel removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showWindow:(id)sender {
|
||||
[(SNTMessageWindow *)self.window fadeIn:sender];
|
||||
}
|
||||
|
||||
- (IBAction)closeWindow:(id)sender {
|
||||
[self.progress cancel];
|
||||
[(SNTMessageWindow *)self.window fadeOut:sender];
|
||||
}
|
||||
|
||||
@@ -119,60 +15,21 @@
|
||||
if (!self.delegate) return;
|
||||
|
||||
if (self.silenceFutureNotifications) {
|
||||
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
|
||||
[self.delegate windowDidCloseSilenceHash:[self messageHash]];
|
||||
} else {
|
||||
[self.delegate windowDidCloseSilenceHash:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender {
|
||||
// SFCertificatePanel expects an NSArray of SecCertificateRef's
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
|
||||
for (MOLCertificate *cert in self.event.signingChain) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
|
||||
modalDelegate:nil
|
||||
didEndSelector:nil
|
||||
contextInfo:nil
|
||||
certificates:certArray
|
||||
showGroup:YES];
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
[self.window setLevel:NSPopUpMenuWindowLevel];
|
||||
[self.window setMovableByWindowBackground:YES];
|
||||
}
|
||||
|
||||
- (IBAction)openEventDetails:(id)sender {
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
#pragma mark Generated properties
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
||||
if (![key isEqualToString:@"event"]) {
|
||||
return [NSSet setWithObject:@"event"];
|
||||
} else {
|
||||
return [NSSet set];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
MOLCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
|
||||
customMessage:self.customMessage];
|
||||
- (NSString *)messageHash {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/santa/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/santa/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
///
|
||||
|
||||
@@ -15,13 +15,16 @@
|
||||
#import "Source/santa/SNTNotificationManager.h"
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
|
||||
@@ -56,14 +59,8 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[self.pendingNotifications removeObject:self.currentWindowController];
|
||||
self.currentWindowController = nil;
|
||||
|
||||
if ([self.pendingNotifications count]) {
|
||||
self.currentWindowController = [self.pendingNotifications firstObject];
|
||||
[self.currentWindowController showWindow:self];
|
||||
if (self.currentWindowController.event.needsBundleHash) {
|
||||
dispatch_async(self.hashBundleBinariesQueue, ^{
|
||||
[self hashBundleBinariesForEvent:self.currentWindowController.event];
|
||||
});
|
||||
}
|
||||
if (self.pendingNotifications.count) {
|
||||
[self showQueuedWindow];
|
||||
} else {
|
||||
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
|
||||
[bc resume];
|
||||
@@ -85,109 +82,67 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[ud setObject:d forKey:silencedNotificationsKey];
|
||||
}
|
||||
|
||||
#pragma mark SNTNotifierXPC protocol methods
|
||||
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode {
|
||||
NSUserNotification *un = [[NSUserNotification alloc] init];
|
||||
un.title = @"Santa";
|
||||
un.hasActionButton = NO;
|
||||
NSString *customMsg;
|
||||
switch (clientmode) {
|
||||
case SNTClientModeMonitor:
|
||||
un.informativeText = @"Switching into Monitor mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
|
||||
if (!customMsg) break;
|
||||
if (!customMsg.length) return;
|
||||
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
break;
|
||||
case SNTClientModeLockdown:
|
||||
un.informativeText = @"Switching into Lockdown mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
|
||||
if (!customMsg) break;
|
||||
if (!customMsg.length) return;
|
||||
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
break;
|
||||
default: return;
|
||||
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
|
||||
for (SNTMessageWindowController *msg in self.pendingNotifications) {
|
||||
if ([[msg messageHash] isEqual:[pendingMsg messageHash]]) return YES;
|
||||
}
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
// See if this binary is already in the list of pending notifications.
|
||||
NSPredicate *predicate =
|
||||
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
|
||||
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
|
||||
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
|
||||
NSString *messageHash = [pendingMsg messageHash];
|
||||
if ([self notificationAlreadyQueued:pendingMsg]) return;
|
||||
|
||||
// See if this binary is silenced.
|
||||
// See if this message is silenced.
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
|
||||
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
|
||||
LOGI(@"Notification silence: date is in the future, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
|
||||
LOGI(@"Notification silence: date is more than one day ago, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else {
|
||||
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
|
||||
LOGI(@"Notification silence: dropping notification for %@", messageHash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
if (!self.currentWindowController) {
|
||||
[self showQueuedWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showQueuedWindow {
|
||||
// Notifications arrive on a background thread but UI updates must happen on the main thread.
|
||||
// This includes making windows.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SNTMessageWindowController *pendingMsg =
|
||||
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
// If a notification isn't currently being displayed, display the incoming one.
|
||||
// This check will generally be redundant, as we'd generally want to check this prior to
|
||||
// starting work on the main thread.
|
||||
if (!self.currentWindowController) {
|
||||
self.currentWindowController = pendingMsg;
|
||||
[pendingMsg showWindow:nil];
|
||||
if (self.currentWindowController.event.needsBundleHash) {
|
||||
self.currentWindowController = [self.pendingNotifications firstObject];
|
||||
[self.currentWindowController showWindow:self];
|
||||
|
||||
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
|
||||
SNTBinaryMessageWindowController *controller =
|
||||
(SNTBinaryMessageWindowController *)self.currentWindowController;
|
||||
dispatch_async(self.hashBundleBinariesQueue, ^{
|
||||
[self hashBundleBinariesForEvent:self.currentWindowController.event];
|
||||
[self hashBundleBinariesForEvent:controller.event withController:controller];
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
#pragma mark SNTBundleNotifierXPC protocol methods
|
||||
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
binaryCount:(uint64_t)binaryCount
|
||||
fileCount:(uint64_t)fileCount
|
||||
hashedCount:(uint64_t)hashedCount {
|
||||
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.currentWindowController.foundFileCountLabel.stringValue =
|
||||
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
|
||||
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark SNTBundleNotifierXPC helper methods
|
||||
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event {
|
||||
self.currentWindowController.foundFileCountLabel.stringValue = @"Searching for files...";
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
|
||||
withController:(SNTBinaryMessageWindowController *)withController {
|
||||
withController.foundFileCountLabel.stringValue = @"Searching for files...";
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
|
||||
@@ -199,22 +154,26 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
// Wait a max of 5 secs for the bundle service
|
||||
// Otherwise abandon bundle hashing and display the blockable event.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
[self updateBlockNotification:event withBundleHash:nil];
|
||||
[withController updateBlockNotification:event withBundleHash:nil];
|
||||
LOGE(@"Timeout connecting to bundle service");
|
||||
return;
|
||||
}
|
||||
|
||||
[[bc remoteObjectProxy] setNotificationListener:self.notificationListener];
|
||||
|
||||
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
|
||||
[self.currentWindowController.progress becomeCurrentWithPendingUnitCount:100];
|
||||
[withController.progress becomeCurrentWithPendingUnitCount:100];
|
||||
|
||||
// Start hashing. Progress is reported to the root NSProgress (currentWindowController.progress).
|
||||
// Start hashing. Progress is reported to the root NSProgress
|
||||
// (currentWindowController.progress).
|
||||
[[bc remoteObjectProxy]
|
||||
hashBundleBinariesForEvent:event
|
||||
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
|
||||
// Revert to displaying the blockable event if we fail to calculate the
|
||||
// bundle hash
|
||||
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
|
||||
if (!bh)
|
||||
return [withController updateBlockNotification:event
|
||||
withBundleHash:nil];
|
||||
|
||||
event.fileBundleHash = bh;
|
||||
event.fileBundleBinaryCount = @(events.count);
|
||||
@@ -238,29 +197,111 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
// Update the UI with the bundle hash. Also make the openEventButton
|
||||
// available.
|
||||
[self updateBlockNotification:event withBundleHash:bh];
|
||||
[withController updateBlockNotification:event withBundleHash:bh];
|
||||
|
||||
[bc invalidate];
|
||||
}];
|
||||
|
||||
[self.currentWindowController.progress resignCurrent];
|
||||
[withController.progress resignCurrent];
|
||||
}
|
||||
|
||||
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
|
||||
if (bundleHash) {
|
||||
[self.currentWindowController.bundleHashLabel setHidden:NO];
|
||||
} else {
|
||||
[self.currentWindowController.bundleHashLabel removeFromSuperview];
|
||||
[self.currentWindowController.bundleHashTitle removeFromSuperview];
|
||||
}
|
||||
self.currentWindowController.event.fileBundleHash = bundleHash;
|
||||
[self.currentWindowController.foundFileCountLabel removeFromSuperview];
|
||||
[self.currentWindowController.hashingIndicator setHidden:YES];
|
||||
[self.currentWindowController.openEventButton setEnabled:YES];
|
||||
#pragma mark SNTNotifierXPC protocol methods
|
||||
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode {
|
||||
UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = @"Santa";
|
||||
|
||||
switch (clientmode) {
|
||||
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;
|
||||
|
||||
content.body = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
break;
|
||||
}
|
||||
});
|
||||
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;
|
||||
|
||||
content.body = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
break;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
|
||||
UNNotificationRequest *req =
|
||||
[UNNotificationRequest requestWithIdentifier:@"clientModeNotification"
|
||||
content:content
|
||||
trigger:nil];
|
||||
|
||||
[un addNotificationRequest:req withCompletionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
|
||||
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 (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
|
||||
SNTBinaryMessageWindowController *pendingMsg =
|
||||
[[SNTBinaryMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
SNTDeviceMessageWindowController *pendingMsg =
|
||||
[[SNTDeviceMessageWindowController alloc] initWithEvent:event message:message];
|
||||
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
#pragma mark SNTBundleNotifierXPC protocol methods
|
||||
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
binaryCount:(uint64_t)binaryCount
|
||||
fileCount:(uint64_t)fileCount
|
||||
hashedCount:(uint64_t)hashedCount {
|
||||
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
|
||||
SNTBinaryMessageWindowController *controller =
|
||||
(SNTBinaryMessageWindowController *)self.currentWindowController;
|
||||
|
||||
if ([controller.event.idx isEqual:event.idx]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
controller.foundFileCountLabel.stringValue =
|
||||
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
|
||||
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
8
Source/santa/Santa.app-adhoc.entitlements
Normal file
8
Source/santa/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>
|
||||
@@ -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>
|
||||
@@ -67,10 +67,6 @@ int main(int argc, const char *argv[]) {
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
|
||||
OSSystemExtensionRequest *req;
|
||||
if (sysxOperation.intValue == 1) {
|
||||
if (![[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
NSLog(@"EnableSystemExtension is disabled");
|
||||
exit(1);
|
||||
}
|
||||
NSLog(@"Requesting SystemExtension activation");
|
||||
req = [OSSystemExtensionRequest activationRequestForExtension:e queue:q];
|
||||
} else if (sysxOperation.intValue == 2) {
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
load(
|
||||
"@build_bazel_rules_apple//apple:macos.bzl",
|
||||
"macos_command_line_application",
|
||||
"macos_kernel_extension",
|
||||
)
|
||||
load("//:helper.bzl", "run_command")
|
||||
load("//:version.bzl", "SANTA_VERSION")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "santa_driver_lib",
|
||||
srcs = [
|
||||
"SantaDecisionManager.cc",
|
||||
"SantaDecisionManager.h",
|
||||
"SantaDriver.cc",
|
||||
"SantaDriver.h",
|
||||
"SantaDriverClient.cc",
|
||||
"SantaDriverClient.h",
|
||||
"main.cc",
|
||||
],
|
||||
copts = [
|
||||
"-mkernel",
|
||||
"-fapple-kext",
|
||||
"-Wno-ossharedptr-misuse",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = [
|
||||
"KERNEL",
|
||||
"APPLE",
|
||||
"NeXT",
|
||||
"SANTA_VERSION=" + SANTA_VERSION,
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLoggingKernel",
|
||||
"//Source/common:SNTPrefixTreeKernel",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
macos_kernel_extension(
|
||||
name = "santa_driver",
|
||||
bundle_id = "com.google.santa-driver",
|
||||
bundle_name = "santa-driver",
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.9",
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":santa_driver_lib"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "kernel_tests_lib",
|
||||
srcs = ["kernel_tests.mm"],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
"IOKit",
|
||||
],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
)
|
||||
|
||||
macos_command_line_application(
|
||||
name = "kernel_tests_bin",
|
||||
bundle_id = "com.google.santa.KernelTests",
|
||||
minimum_os_version = "10.9",
|
||||
deps = [":kernel_tests_lib"],
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "kernel_tests",
|
||||
srcs = [
|
||||
":kernel_tests_bin",
|
||||
":santa_driver",
|
||||
],
|
||||
cmd = """
|
||||
env
|
||||
function sigint() {
|
||||
echo "\nInterrupted, unloading driver."
|
||||
sudo kextunload -b com.google.santa-driver >/dev/null
|
||||
exit 1
|
||||
}
|
||||
unzip -o $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
echo "Launching Kernel Tests as root. You may be prompted for your sudo password."
|
||||
trap sigint INT
|
||||
sudo $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/kernel_tests_bin
|
||||
echo "Tests complete."
|
||||
if kextstat | grep com.google.santa-driver; then
|
||||
sudo kextunload -b com.google.santa-driver >/dev/null
|
||||
fi
|
||||
""",
|
||||
)
|
||||
@@ -1,51 +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>CFBundleExecutable</key>
|
||||
<string>santa-driver</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa-driver</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>santa-driver</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>KEXT</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>IOKitPersonalities</key>
|
||||
<dict>
|
||||
<key>SantaDriver</key>
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa-driver</string>
|
||||
<key>IOClass</key>
|
||||
<string>com_google_SantaDriver</string>
|
||||
<key>IOMatchCategory</key>
|
||||
<string>com_google_SantaDriver</string>
|
||||
<key>IOProviderClass</key>
|
||||
<string>IOResources</string>
|
||||
<key>IOResourceMatch</key>
|
||||
<string>IOKit</string>
|
||||
<key>IOUserClientClass</key>
|
||||
<string>com_google_SantaDriverClient</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Google LLC.</string>
|
||||
<key>OSBundleLibraries</key>
|
||||
<dict>
|
||||
<key>com.apple.kpi.bsd</key>
|
||||
<string>9.0.0</string>
|
||||
<key>com.apple.kpi.iokit</key>
|
||||
<string>9.0.0</string>
|
||||
<key>com.apple.kpi.libkern</key>
|
||||
<string>9.0.0</string>
|
||||
<key>com.apple.kpi.mach</key>
|
||||
<string>9.0.0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,843 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/santa_driver/SantaDecisionManager.h"
|
||||
|
||||
// This is a made-up KAUTH_FILEOP constant which represents a
|
||||
// KAUTH_VNODE_WRITE_DATA event that gets passed to SantaDecisionManager's
|
||||
// FileOpCallback method. The KAUTH_FILEOP_* constants are defined in
|
||||
// sys/kauth.h and run from 1--7. KAUTH_VNODE_WRITE_DATA is already defined as
|
||||
// 4 so it overlaps with the other KAUTH_FILEOP_* constants and can't be used.
|
||||
// We define KAUTH_FILEOP_WRITE as something much greater than 7.
|
||||
#define KAUTH_FILEOP_WRITE 100
|
||||
|
||||
#define super OSObject
|
||||
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
|
||||
|
||||
#pragma mark Object Lifecycle
|
||||
|
||||
template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t) {
|
||||
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::init() {
|
||||
if (!super::init()) return false;
|
||||
|
||||
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
|
||||
sdm_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
|
||||
root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(5000, 2);
|
||||
non_root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(500, 2);
|
||||
vnode_pid_map_ = new SantaCache<santa_vnode_id_t, uint64_t>(2000, 5);
|
||||
compiler_pid_set_ = new SantaCache<pid_t, pid_t>(500, 5);
|
||||
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
if (!decision_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
if (!log_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
client_pid_ = 0;
|
||||
root_fsid_ = 0;
|
||||
|
||||
// Setup file modification prefix filter.
|
||||
filemod_prefix_filter_ = new SNTPrefixTree();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::free() {
|
||||
delete root_decision_cache_;
|
||||
delete non_root_decision_cache_;
|
||||
delete vnode_pid_map_;
|
||||
|
||||
StopPidMonitorThreads();
|
||||
|
||||
if (decision_dataqueue_lock_) {
|
||||
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
|
||||
decision_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (log_dataqueue_lock_) {
|
||||
lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
|
||||
log_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_attr_) {
|
||||
lck_attr_free(sdm_lock_attr_);
|
||||
sdm_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_) {
|
||||
lck_grp_free(sdm_lock_grp_);
|
||||
sdm_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_attr_) {
|
||||
lck_grp_attr_free(sdm_lock_grp_attr_);
|
||||
sdm_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
|
||||
OSSafeReleaseNULL(decision_dataqueue_);
|
||||
OSSafeReleaseNULL(log_dataqueue_);
|
||||
|
||||
delete filemod_prefix_filter_;
|
||||
|
||||
super::free();
|
||||
}
|
||||
|
||||
#pragma mark Client Management
|
||||
|
||||
void SantaDecisionManager::ConnectClient(pid_t pid) {
|
||||
if (!pid) return;
|
||||
|
||||
client_pid_ = pid;
|
||||
|
||||
// Determine root fsid
|
||||
vfs_context_t ctx = vfs_context_create(nullptr);
|
||||
if (ctx) {
|
||||
vnode_t root = vfs_rootvnode();
|
||||
if (root) {
|
||||
root_fsid_ = GetVnodeIDForVnode(ctx, root).fsid;
|
||||
vnode_put(root);
|
||||
}
|
||||
vfs_context_rele(ctx);
|
||||
}
|
||||
|
||||
// Any decisions made while the daemon wasn't
|
||||
// connected should be cleared
|
||||
ClearCache();
|
||||
|
||||
failed_decision_queue_requests_ = 0;
|
||||
failed_log_queue_requests_ = 0;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DisconnectClient(bool itDied, pid_t pid) {
|
||||
if (client_pid_ == 0 || (pid > 0 && pid != client_pid_)) return;
|
||||
client_pid_ = 0;
|
||||
|
||||
// Ask santad to shutdown, in case it's running.
|
||||
if (!itDied) {
|
||||
auto message = new santa_message_t;
|
||||
message->action = ACTION_REQUEST_SHUTDOWN;
|
||||
PostToDecisionQueue(message);
|
||||
delete message;
|
||||
decision_dataqueue_->setNotificationPort(nullptr);
|
||||
} else {
|
||||
// If the client died, reset the data queues so when it reconnects
|
||||
// it doesn't get swamped straight away.
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->release();
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->release();
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
|
||||
// Reset the filter.
|
||||
// On startup the daemon will add prefixes as needed.
|
||||
FilemodPrefixFilterReset();
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::ClientConnected() const {
|
||||
if (client_pid_ <= 0) return false;
|
||||
auto p = proc_find(client_pid_);
|
||||
auto is_exiting = false;
|
||||
if (p) {
|
||||
is_exiting = proc_exiting(p);
|
||||
proc_rele(p);
|
||||
}
|
||||
return (client_pid_ > 0 && !is_exiting);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::SetLogPort(mach_port_t port) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
IOMemoryDescriptor *r = decision_dataqueue_->getMemoryDescriptor();
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return r;
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
IOMemoryDescriptor *r = log_dataqueue_->getMemoryDescriptor();
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return r;
|
||||
}
|
||||
|
||||
#pragma mark Listener Control
|
||||
|
||||
kern_return_t SantaDecisionManager::StartListener() {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
vnode_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
|
||||
#pragma clang diagnostic pop
|
||||
if (!vnode_listener_) return kIOReturnInternalError;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
fileop_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_FILEOP, fileop_scope_callback,
|
||||
reinterpret_cast<void *>(this));
|
||||
#pragma clang diagnostic pop
|
||||
if (!fileop_listener_) return kIOReturnInternalError;
|
||||
|
||||
LOGD("Listeners started.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
kern_return_t SantaDecisionManager::StopListener() {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
kauth_unlisten_scope(vnode_listener_);
|
||||
vnode_listener_ = nullptr;
|
||||
|
||||
kauth_unlisten_scope(fileop_listener_);
|
||||
fileop_listener_ = nullptr;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// Wait for any active invocations to finish before returning
|
||||
do {
|
||||
IOSleep(5);
|
||||
} while (listener_invocations_);
|
||||
|
||||
// Delete any cached decisions
|
||||
ClearCache();
|
||||
|
||||
LOGD("Listeners stopped.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
# pragma mark Monitoring PIDs
|
||||
|
||||
// Arguments that are passed to pid_monitor thread.
|
||||
typedef struct {
|
||||
pid_t pid; // process to monitor
|
||||
SantaDecisionManager *sdm; // reference to SantaDecisionManager
|
||||
} pid_monitor_info;
|
||||
|
||||
// Function executed in its own thread used to monitor a compiler process for
|
||||
// termination and then remove the process pid from cache of compiler pids.
|
||||
static void pid_monitor(void *param, __unused wait_result_t wait_result) {
|
||||
pid_monitor_info *info = (pid_monitor_info *)param;
|
||||
if (info && info->sdm) {
|
||||
uint32_t sleep_time = info->sdm->PidMonitorSleepTimeMilliseconds();
|
||||
while (!info->sdm->PidMonitorThreadsShouldExit()) {
|
||||
proc_t proc = proc_find(info->pid);
|
||||
if (!proc) break;
|
||||
proc_rele(proc);
|
||||
IOSleep(sleep_time);
|
||||
}
|
||||
info->sdm->ForgetCompilerPid(info->pid);
|
||||
info->sdm->DecrementPidMonitorThreadCount();
|
||||
}
|
||||
thread_terminate(current_thread());
|
||||
}
|
||||
|
||||
// TODO(nguyenphillip): Look at moving pid monitoring out of SDM entirely,
|
||||
// maybe by creating a dedicated class to do this that SDM could then query.
|
||||
void SantaDecisionManager::MonitorCompilerPidForExit(pid_t pid) {
|
||||
// Don't start any new threads if compiler_pid_set_ doesn't exist.
|
||||
if (!compiler_pid_set_) return;
|
||||
auto info = new pid_monitor_info;
|
||||
info->pid = pid;
|
||||
info->sdm = this;
|
||||
thread_t thread = THREAD_NULL;
|
||||
IncrementPidMonitorThreadCount();
|
||||
if (KERN_SUCCESS != kernel_thread_start(pid_monitor, (void *)info, &thread)) {
|
||||
LOGE("couldn't start pid monitor thread");
|
||||
DecrementPidMonitorThreadCount();
|
||||
}
|
||||
thread_deallocate(thread);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::ForgetCompilerPid(pid_t pid) {
|
||||
if (compiler_pid_set_) compiler_pid_set_->remove(pid);
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PidMonitorThreadsShouldExit() const {
|
||||
return compiler_pid_set_ == nullptr;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::StopPidMonitorThreads() {
|
||||
// Each pid_monitor thread checks for the existence of compiler_pid_set_.
|
||||
// As soon as they see that it's gone, they should terminate and decrement
|
||||
// SantaDecisionManager's pid_monitor_thread_count. When this count decreases
|
||||
// to zero all threads have finished.
|
||||
auto temp = compiler_pid_set_;
|
||||
compiler_pid_set_ = nullptr;
|
||||
delete temp;
|
||||
|
||||
// Sleep time between checks starts at 10 ms, but increases to 5 sec after
|
||||
// 10 sec have passed without the thread count dropping to 0.
|
||||
unsigned int sleep_time_milliseconds = 10;
|
||||
unsigned int total_wait_time = 0;
|
||||
|
||||
while (pid_monitor_thread_count_ > 0) {
|
||||
if (sleep_time_milliseconds == 10) {
|
||||
total_wait_time += sleep_time_milliseconds;
|
||||
if (total_wait_time >= 10000) {
|
||||
sleep_time_milliseconds = 5000;
|
||||
LOGD("Waited %d ms for pid monitor threads to quit, switching sleep"
|
||||
"time to %d ms", total_wait_time, sleep_time_milliseconds);
|
||||
}
|
||||
}
|
||||
IOSleep(sleep_time_milliseconds);
|
||||
}
|
||||
LOGD("Pid monitor threads stopped.");
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t SantaDecisionManager::PidMonitorSleepTimeMilliseconds() const {
|
||||
return kPidMonitorSleepTimeMilliseconds;
|
||||
}
|
||||
|
||||
#pragma mark Cache Management
|
||||
|
||||
/**
|
||||
Return the correct cache for a given identifier.
|
||||
|
||||
@param identifier The identifier
|
||||
@return SantaCache* The cache to use
|
||||
*/
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *SantaDecisionManager::CacheForIdentifier(
|
||||
const santa_vnode_id_t identifier) {
|
||||
return (identifier.fsid == root_fsid_) ? root_decision_cache_ : non_root_decision_cache_;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::AddToCache(
|
||||
santa_vnode_id_t identifier, santa_action_t decision, uint64_t microsecs) {
|
||||
auto decision_cache = CacheForIdentifier(identifier);
|
||||
|
||||
switch (decision) {
|
||||
case ACTION_REQUEST_BINARY:
|
||||
decision_cache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ACK:
|
||||
decision_cache->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
|
||||
((uint64_t)ACTION_REQUEST_BINARY << 56));
|
||||
break;
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
case ACTION_RESPOND_DENY: {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
if (!decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
|
||||
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
decision_cache->set(identifier, val, 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
wakeup((void *)identifier.unsafe_simple_id());
|
||||
}
|
||||
|
||||
void SantaDecisionManager::RemoveFromCache(santa_vnode_id_t identifier) {
|
||||
if (unlikely(identifier.fsid == 0 && identifier.fileid == 0)) return;
|
||||
CacheForIdentifier(identifier)->remove(identifier);
|
||||
wakeup((void *)identifier.unsafe_simple_id());
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::RootCacheCount() const {
|
||||
return root_decision_cache_->count();
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::NonRootCacheCount() const {
|
||||
return non_root_decision_cache_->count();
|
||||
}
|
||||
|
||||
void SantaDecisionManager::ClearCache(bool non_root_only) {
|
||||
if (!non_root_only) root_decision_cache_->clear();
|
||||
non_root_decision_cache_->clear();
|
||||
}
|
||||
|
||||
void SantaDecisionManager::CacheBucketCount(
|
||||
uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
|
||||
root_decision_cache_->bucket_counts(per_bucket_counts, array_size, start_bucket);
|
||||
}
|
||||
|
||||
#pragma mark Decision Fetching
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromCache(santa_vnode_id_t identifier) {
|
||||
auto result = ACTION_UNSET;
|
||||
uint64_t decision_time = 0;
|
||||
|
||||
auto decision_cache = CacheForIdentifier(identifier);
|
||||
|
||||
uint64_t cache_val = decision_cache->get(identifier);
|
||||
if (cache_val == 0) return result;
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
result = (santa_action_t)(cache_val >> 56);
|
||||
decision_time = (cache_val & ~(0xFF00000000000000));
|
||||
|
||||
if (RESPONSE_VALID(result)) {
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
|
||||
if (expiry_time < GetCurrentUptime()) {
|
||||
decision_cache->remove(identifier);
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
santa_message_t *message, santa_vnode_id_t identifier) {
|
||||
auto return_action = ACTION_UNSET;
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_sec_t secs = 0;
|
||||
clock_usec_t microsecs = 0;
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
uint64_t uptime = (secs * 1000000) + microsecs;
|
||||
#endif
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Add pending request to cache, to be replaced
|
||||
// by daemon with actual response.
|
||||
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
|
||||
|
||||
// Send request to daemon.
|
||||
if (!PostToDecisionQueue(message)) {
|
||||
LOGE("Failed to queue request for %s.", message->path);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
// Check the cache every kRequestLoopSleepMilliseconds. Break this loop and send the request
|
||||
// again if kRequestCacheChecks is reached. Don't break the loop if the daemon is working on the
|
||||
// request, indicated with ACTION_RESPOND_ACK.
|
||||
auto cache_check_count = 0;
|
||||
do {
|
||||
msleep((void *)message->vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
|
||||
return_action = GetFromCache(identifier);
|
||||
} while (ClientConnected() &&
|
||||
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
|
||||
|| (return_action == ACTION_RESPOND_ACK)));
|
||||
} while (!RESPONSE_VALID(return_action) && ClientConnected());
|
||||
|
||||
// If response is still not valid, the daemon exited
|
||||
if (!RESPONSE_VALID(return_action)) {
|
||||
LOGE("Daemon process did not respond correctly. Allowing executions "
|
||||
"until it comes back. Executable path: %s", message->path);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
LOGD("Decision time: %4lldms (%s)",
|
||||
(((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
|
||||
#endif
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const santa_vnode_id_t vnode_id) {
|
||||
while (true) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id);
|
||||
|
||||
// If item was in cache with a valid response, return it.
|
||||
// If item is in cache but hasn't received a response yet, sleep for a bit.
|
||||
// If item is not in cache, break out of loop to send request to daemon.
|
||||
if (RESPONSE_VALID(return_action)) {
|
||||
return return_action;
|
||||
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
|
||||
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
|
||||
// until AddToCache is called, indicating a response has arrived.
|
||||
msleep((void *)vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
int name_len = MAXPATHLEN;
|
||||
path[MAXPATHLEN - 1] = 0;
|
||||
|
||||
if (vn_getpath(vp, path, &name_len) == ENOSPC) {
|
||||
return ACTION_RESPOND_TOOLONG;
|
||||
}
|
||||
|
||||
auto message = NewMessage(cred);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
message->action = ACTION_REQUEST_BINARY;
|
||||
message->vnode_id = vnode_id;
|
||||
proc_name(message->ppid, message->pname, sizeof(message->pname));
|
||||
auto return_action = GetFromDaemon(message, vnode_id);
|
||||
delete message;
|
||||
return return_action;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (++failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d decision requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (failed_log_queue_requests_++ == 0) {
|
||||
LOGW("Dropping log queue messages");
|
||||
}
|
||||
} else {
|
||||
if (failed_log_queue_requests_ > 0) {
|
||||
failed_log_queue_requests_--;
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
#pragma mark Invocation Tracking & PID comparison
|
||||
|
||||
void SantaDecisionManager::IncrementListenerInvocations() {
|
||||
OSIncrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DecrementListenerInvocations() {
|
||||
OSDecrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::IncrementPidMonitorThreadCount() {
|
||||
OSIncrementAtomic(&pid_monitor_thread_count_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DecrementPidMonitorThreadCount() {
|
||||
OSDecrementAtomic(&pid_monitor_thread_count_);
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::IsCompilerProcess(pid_t pid) {
|
||||
for (;;) {
|
||||
// Find the parent pid.
|
||||
proc_t proc = proc_find(pid);
|
||||
if (!proc) return false;
|
||||
pid_t ppid = proc_ppid(proc);
|
||||
proc_rele(proc);
|
||||
// Quit if process is launchd or has no parent.
|
||||
if (ppid == 0 || pid == ppid) break;
|
||||
pid_t val = compiler_pid_set_->get(pid);
|
||||
// If pid was in compiler_pid_set_ then make sure that it has the same
|
||||
// parent pid as when it was set.
|
||||
if (val) return val == ppid;
|
||||
// If pid not in the set, then quit unless we want to check ancestors.
|
||||
if (!kCheckCompilerAncestors) break;
|
||||
pid = ppid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma mark Callbacks
|
||||
|
||||
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vfs_context_t ctx,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Get ID for the vnode
|
||||
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
if (vnode_id.fsid == 0 && vnode_id.fileid == 0) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Fetch decision
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id);
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
|
||||
auto proc = vfs_context_proc(ctx);
|
||||
if (proc) {
|
||||
pid_t pid = proc_pid(proc);
|
||||
pid_t ppid = proc_ppid(proc);
|
||||
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
|
||||
uint64_t val = ((uint64_t)pid << 32) | ((uint64_t)ppid & 0xFFFFFFFF);
|
||||
vnode_pid_map_->set(vnode_id, val);
|
||||
if (returnedAction == ACTION_RESPOND_ALLOW_COMPILER && ppid != 0) {
|
||||
// Do some additional bookkeeping for compilers:
|
||||
// We associate the pid with a compiler so that when we see it later
|
||||
// in the context of a KAUTH_FILEOP event, we'll recognize it.
|
||||
compiler_pid_set_->set(pid, ppid);
|
||||
// And start polling for the compiler process termination, so that we
|
||||
// can remove the pid from our cache of compiler pids.
|
||||
MonitorCompilerPidForExit(pid);
|
||||
}
|
||||
}
|
||||
return KAUTH_RESULT_ALLOW;
|
||||
}
|
||||
case ACTION_RESPOND_DENY:
|
||||
*errno = EPERM;
|
||||
return KAUTH_RESULT_DENY;
|
||||
case ACTION_RESPOND_TOOLONG:
|
||||
*errno = ENAMETOOLONG;
|
||||
return KAUTH_RESULT_DENY;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
// we don't break user's machines.
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (!ClientConnected()) return;
|
||||
|
||||
// KAUTH_FILEOP_CLOSE implies KAUTH_FILEOP_CLOSE_MODIFIED, so remove it from the cache.
|
||||
if (action == KAUTH_FILEOP_CLOSE) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
RemoveFromCache(GetVnodeIDForVnode(context, vp));
|
||||
vfs_context_rele(context);
|
||||
}
|
||||
|
||||
// Don't log santad fileops.
|
||||
if (proc_selfpid() == client_pid_) return;
|
||||
|
||||
if (vp && action == KAUTH_FILEOP_EXEC) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
auto vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
vfs_context_rele(context);
|
||||
|
||||
auto message = NewMessage(nullptr);
|
||||
message->vnode_id = vnode_id;
|
||||
message->action = ACTION_NOTIFY_EXEC;
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
|
||||
// The vnode scope gets posix_spawn pid and ppid properly. The fileop scope does not.
|
||||
// Get pid and ppid cached during vnode execution.
|
||||
uint64_t val = vnode_pid_map_->get(vnode_id);
|
||||
if (val) {
|
||||
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
|
||||
message->pid = (pid_t)(val >> 32);
|
||||
message->ppid = (pid_t)(val & ~0xFFFFFFFF00000000);
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
|
||||
// For transitive whitelisting decisions, we must check for KAUTH_FILEOP_CLOSE events from a
|
||||
// known compiler process. But we must also check for KAUTH_FILEOP_RENAME events because clang
|
||||
// under Xcode 9 will, if the output file already exists, write to a temp file, delete the
|
||||
// existing file, then rename the temp file, without ever closing it. So in this scenario,
|
||||
// the KAUTH_FILEOP_RENAME is the only chance we have of whitelisting the output.
|
||||
if (action == KAUTH_FILEOP_CLOSE || (action == KAUTH_FILEOP_RENAME && new_path)) {
|
||||
auto message = NewMessage(nullptr);
|
||||
if (IsCompilerProcess(message->pid)) {
|
||||
// Fill out the rest of the message details and send it to the decision queue.
|
||||
auto context = vfs_context_create(nullptr);
|
||||
vnode_t real_vp = vp;
|
||||
// We have to manually look up the vnode pointer from new_path for KAUTH_FILEOP_RENAME.
|
||||
if (!real_vp && new_path && ERR_SUCCESS == vnode_lookup(new_path, 0, &real_vp, context)) {
|
||||
vnode_put(real_vp);
|
||||
}
|
||||
if (real_vp) message->vnode_id = GetVnodeIDForVnode(context, real_vp);
|
||||
vfs_context_rele(context);
|
||||
message->action = ACTION_NOTIFY_WHITELIST;
|
||||
const char *real_path = (action == KAUTH_FILEOP_CLOSE) ? path : new_path;
|
||||
strlcpy(message->path, real_path, sizeof(message->path));
|
||||
proc_name(message->pid, message->pname, sizeof(message->pname));
|
||||
PostToDecisionQueue(message);
|
||||
// Add a temporary allow rule to the decision cache for this vnode_id
|
||||
// while SNTCompilerController decides whether or not to add a
|
||||
// permanent rule for the new file to the rules database. This is
|
||||
// because checking if the file is a Mach-O binary and hashing it might
|
||||
// not finish before an attempt to execute it.
|
||||
AddToCache(message->vnode_id, ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE, 0);
|
||||
}
|
||||
delete message;
|
||||
// Don't need to do anything else for FILEOP_CLOSE, but FILEOP_RENAME should fall through.
|
||||
if (action == KAUTH_FILEOP_CLOSE) return;
|
||||
}
|
||||
|
||||
// Filter out modifications to locations that are definitely
|
||||
// not useful or made by santad.
|
||||
if (!filemod_prefix_filter_->HasPrefix(path)) {
|
||||
auto message = NewMessage(nullptr);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
|
||||
proc_name(message->pid, message->pname, sizeof(message->pname));
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_WRITE:
|
||||
// This is actually a KAUTH_VNODE_WRITE_DATA event.
|
||||
message->action = ACTION_NOTIFY_WRITE;
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
message->action = ACTION_NOTIFY_RENAME;
|
||||
break;
|
||||
case KAUTH_FILEOP_LINK:
|
||||
message->action = ACTION_NOTIFY_LINK;
|
||||
break;
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
message->action = ACTION_NOTIFY_EXCHANGE;
|
||||
break;
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
message->action = ACTION_NOTIFY_DELETE;
|
||||
break;
|
||||
default:
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
extern "C" int fileop_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (unlikely(sdm == nullptr)) {
|
||||
LOGE("fileop_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = nullptr;
|
||||
char *path = nullptr;
|
||||
char *new_path = nullptr;
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_CLOSE:
|
||||
// We only care about KAUTH_FILEOP_CLOSE events where the closed file
|
||||
// was modified.
|
||||
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED))
|
||||
return KAUTH_RESULT_DEFER;
|
||||
// Intentional fallthrough to get vnode reference.
|
||||
[[fallthrough]];
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
case KAUTH_FILEOP_EXEC:
|
||||
vp = reinterpret_cast<vnode_t>(arg0);
|
||||
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
case KAUTH_FILEOP_LINK:
|
||||
path = reinterpret_cast<char *>(arg0);
|
||||
new_path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
default:
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
sdm->IncrementListenerInvocations();
|
||||
sdm->FileOpCallback(action, vp, path, new_path);
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (unlikely(sdm == nullptr)) {
|
||||
LOGE("vnode_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
|
||||
|
||||
// We only care about regular files.
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
if ((action & (int)KAUTH_VNODE_EXECUTE) && !(action & (int)KAUTH_VNODE_ACCESS)) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
vp,
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
} else if (action & (int)KAUTH_VNODE_WRITE_DATA || action & (int)KAUTH_VNODE_APPEND_DATA) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
if (!(action & (int)KAUTH_VNODE_ACCESS)) {
|
||||
auto vnode_id = sdm->GetVnodeIDForVnode(reinterpret_cast<vfs_context_t>(arg0), vp);
|
||||
sdm->RemoveFromCache(vnode_id);
|
||||
}
|
||||
char path[MAXPATHLEN];
|
||||
int pathlen = MAXPATHLEN;
|
||||
vn_getpath(vp, path, &pathlen);
|
||||
// KAUTH_VNODE_WRITE_DATA events are translated into fake KAUTH_FILEOP_WRITE
|
||||
// events so that we can handle them in the FileOpCallback function.
|
||||
sdm->FileOpCallback(KAUTH_FILEOP_WRITE, vp, path, nullptr);
|
||||
sdm->DecrementListenerInvocations();
|
||||
}
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
#define SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
|
||||
#include <IOKit/IODataQueueShared.h>
|
||||
#include <IOKit/IOLib.h>
|
||||
#include <IOKit/IOMemoryDescriptor.h>
|
||||
#include <IOKit/IOSharedDataQueue.h>
|
||||
#include <sys/kauth.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
|
||||
///
|
||||
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
|
||||
/// and responding to the request appropriately.
|
||||
///
|
||||
/// Documentation on the Kauth parts can be found here:
|
||||
/// https://developer.apple.com/library/mac/technotes/tn2127/_index.html
|
||||
///
|
||||
class SantaDecisionManager : public OSObject {
|
||||
OSDeclareDefaultStructors(SantaDecisionManager);
|
||||
|
||||
public:
|
||||
/// Used for initialization after instantiation.
|
||||
bool init() override;
|
||||
|
||||
/// Called automatically when retain count drops to 0.
|
||||
void free() override;
|
||||
|
||||
/**
|
||||
Called by SantaDriverClient during connection to provide the shared
|
||||
dataqueue memory to the client for the decision queue.
|
||||
*/
|
||||
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
|
||||
|
||||
/**
|
||||
Called by SantaDriverClient during connection to provide the shared
|
||||
dataqueue memory to the client for the logging queue.
|
||||
*/
|
||||
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
|
||||
|
||||
/**
|
||||
Called by SantaDriverClient when a client connects to the decision queue,
|
||||
providing the pid of the client process.
|
||||
*/
|
||||
void ConnectClient(pid_t pid);
|
||||
|
||||
/// Called by SantaDriverClient when a client disconnects
|
||||
void DisconnectClient(bool itDied = false, pid_t pid = proc_selfpid());
|
||||
|
||||
/// Returns whether a client is currently connected or not.
|
||||
bool ClientConnected() const;
|
||||
|
||||
/// Sets the Mach port for notifying the decision queue.
|
||||
void SetDecisionPort(mach_port_t port);
|
||||
|
||||
/// Sets the Mach port for notifying the log queue.
|
||||
void SetLogPort(mach_port_t port);
|
||||
|
||||
/// Starts the kauth listeners.
|
||||
kern_return_t StartListener();
|
||||
|
||||
/**
|
||||
Stops the kauth listeners. After stopping new callback requests, waits
|
||||
until all current invocations have finished before clearing the cache and
|
||||
returning.
|
||||
*/
|
||||
kern_return_t StopListener();
|
||||
|
||||
/**
|
||||
This spins off a new thread for each process that we monitor. Generally the
|
||||
threads should be short-lived, since they die as soon as their associated
|
||||
compiler process dies.
|
||||
*/
|
||||
void MonitorCompilerPidForExit(pid_t pid);
|
||||
|
||||
/// Remove the given pid from cache of compiler pids.
|
||||
void ForgetCompilerPid(pid_t pid);
|
||||
|
||||
/// Returns true when SantaDecisionManager wants monitor threads to exit.
|
||||
bool PidMonitorThreadsShouldExit() const;
|
||||
|
||||
/**
|
||||
Stops the pid monitor threads. Waits until all threads have stopped before
|
||||
returning. This also frees the compiler_pid_set_. Returns true if all
|
||||
threads exited cleanly. Returns false if timed out while waiting.
|
||||
*/
|
||||
bool StopPidMonitorThreads();
|
||||
|
||||
/// Returns how long pid monitor should sleep between termination checks.
|
||||
uint32_t PidMonitorSleepTimeMilliseconds() const;
|
||||
|
||||
/// Adds a decision to the cache, with a timestamp.
|
||||
void AddToCache(santa_vnode_id_t identifier, const santa_action_t decision,
|
||||
const uint64_t microsecs = GetCurrentUptime());
|
||||
|
||||
/**
|
||||
Fetches a response from the cache, first checking to see if the entry
|
||||
has expired.
|
||||
*/
|
||||
santa_action_t GetFromCache(santa_vnode_id_t identifier);
|
||||
|
||||
/// Checks to see if a given identifier is in the cache and removes it.
|
||||
void RemoveFromCache(santa_vnode_id_t identifier);
|
||||
|
||||
/// Returns the number of entries in the cache.
|
||||
uint64_t RootCacheCount() const;
|
||||
uint64_t NonRootCacheCount() const;
|
||||
|
||||
/**
|
||||
Clears the cache(s). If non_root_only is true, only the non-root cache
|
||||
is cleared.
|
||||
*/
|
||||
void ClearCache(bool non_root_only = false);
|
||||
|
||||
/**
|
||||
Fills out the per_bucket_counts array with the number of items in each
|
||||
bucket in the non-root decision cache.
|
||||
|
||||
@param per_bucket_counts An array of uint16_t's to fill in with the number
|
||||
of items in each bucket. The size of this array is expected to equal
|
||||
array_size.
|
||||
@param array_size The size of the per_bucket_counts array on input. Upon
|
||||
return this will be updated to the number of slots that were actually used.
|
||||
@param start_bucket If non-zero this is the bucket in the cache to start
|
||||
from. Upon return this will be the next numbered bucket to start from for
|
||||
subsequent requests.
|
||||
*/
|
||||
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size,
|
||||
uint64_t *start_bucket);
|
||||
|
||||
/// Increments the count of active callbacks pending.
|
||||
void IncrementListenerInvocations();
|
||||
|
||||
/// Decrements the count of active callbacks pending.
|
||||
void DecrementListenerInvocations();
|
||||
|
||||
/// Increments the count of active pid monitor threads.
|
||||
void IncrementPidMonitorThreadCount();
|
||||
|
||||
/// Decrements the count of active pid monitor threads.
|
||||
void DecrementPidMonitorThreadCount();
|
||||
|
||||
/**
|
||||
Determine if pid belongs to a compiler process. When
|
||||
kCheckCompilerAncestors is set to true, this also checks all ancestor
|
||||
processes of the pid.
|
||||
*/
|
||||
bool IsCompilerProcess(pid_t pid);
|
||||
|
||||
/**
|
||||
Add a file modification prefix filter.
|
||||
*/
|
||||
inline IOReturn FilemodPrefixFilterAdd(const char *prefix,
|
||||
uint64_t *node_count = nullptr) {
|
||||
return filemod_prefix_filter_->AddPrefix(prefix, node_count);
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the file modification prefix filter tree.
|
||||
*/
|
||||
inline void FilemodPrefixFilterReset() { filemod_prefix_filter_->Reset(); }
|
||||
|
||||
/**
|
||||
Fetches the vnode_id for a given vnode.
|
||||
|
||||
@param ctx The VFS context to use.
|
||||
@param vp The Vnode to get the ID for
|
||||
@return santa_vnode_id_t The Vnode ID.
|
||||
*/
|
||||
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx,
|
||||
const vnode_t vp) {
|
||||
struct vnode_attr vap;
|
||||
VATTR_INIT(&vap);
|
||||
VATTR_WANTED(&vap, va_fsid);
|
||||
VATTR_WANTED(&vap, va_fileid);
|
||||
vnode_getattr(vp, &vap, ctx);
|
||||
return {.fsid = vap.va_fsid, .fileid = vap.va_fileid};
|
||||
}
|
||||
|
||||
/**
|
||||
Vnode Callback
|
||||
|
||||
@param cred The kauth credential for this request.
|
||||
@param ctx The VFS context for this request.
|
||||
@param vp The Vnode for this request.
|
||||
@param errno A pointer to return an errno style error.
|
||||
@return int A valid KAUTH_RESULT_*.
|
||||
*/
|
||||
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
|
||||
const vnode_t vp, int *errno);
|
||||
/**
|
||||
FileOp Callback
|
||||
|
||||
@param action The performed action
|
||||
@param vp The Vnode for this request. May be nullptr.
|
||||
@param path The path being operated on.
|
||||
@param new_path The target path for moves and links.
|
||||
*/
|
||||
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path,
|
||||
const char *new_path);
|
||||
|
||||
private:
|
||||
/**
|
||||
While waiting for a response from the daemon, this is the maximum number of
|
||||
milliseconds to sleep for before checking the cache for a response.
|
||||
*/
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
|
||||
|
||||
/**
|
||||
While waiting for a response from the daemon, this is the maximum number
|
||||
cache checks before re-sending the request.
|
||||
*/
|
||||
static const uint32_t kRequestCacheChecks = 5;
|
||||
|
||||
/**
|
||||
The maximum number of milliseconds a cached deny message should be
|
||||
considered valid.
|
||||
*/
|
||||
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
static const uint32_t kMaxCacheSize = 10000;
|
||||
|
||||
/// Maximum number of PostToDecisionQueue failures to allow.
|
||||
static const uint32_t kMaxDecisionQueueFailures = 10;
|
||||
|
||||
/**
|
||||
The maximum number of messages that can be kept in the decision data queue
|
||||
at any time.
|
||||
*/
|
||||
static const uint32_t kMaxDecisionQueueEvents = 512;
|
||||
|
||||
/**
|
||||
The maximum number of messages that can be kept in the logging data queue
|
||||
at any time.
|
||||
*/
|
||||
static const uint32_t kMaxLogQueueEvents = 2048;
|
||||
|
||||
/// How long pid monitor thread should sleep between termination checks.
|
||||
static const uint32_t kPidMonitorSleepTimeMilliseconds = 1000;
|
||||
|
||||
/**
|
||||
When set to true, Santa will check all ancestors of a process to determine
|
||||
if it is a compiler.
|
||||
TODO(nguyenphillip): this setting (and others above) should be configurable.
|
||||
*/
|
||||
static const bool kCheckCompilerAncestors = false;
|
||||
|
||||
/**
|
||||
Fetches a response from the daemon. Handles both daemon death
|
||||
and failure to post messages to the daemon.
|
||||
|
||||
@param message The message to send to the daemon
|
||||
@param identifier The vnode ID string for this request
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t GetFromDaemon(santa_message_t *message,
|
||||
santa_vnode_id_t identifier);
|
||||
|
||||
/**
|
||||
Fetches an execution decision for a file, first using the cache and then
|
||||
by sending a message to the daemon and waiting until a response arrives.
|
||||
If a daemon isn't connected, will allow execution and cache, logging
|
||||
the path to the executed file.
|
||||
|
||||
@param cred The credential for this request.
|
||||
@param vp The Vnode for this request.
|
||||
@param vnode_id The ID for this vnode.
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t FetchDecision(const kauth_cred_t cred, const vnode_t vp,
|
||||
const santa_vnode_id_t vnode_id);
|
||||
|
||||
/**
|
||||
Posts the requested message to the decision data queue.
|
||||
|
||||
@param message The message to send
|
||||
@return bool true if sending was successful.
|
||||
*/
|
||||
bool PostToDecisionQueue(santa_message_t *message);
|
||||
|
||||
/**
|
||||
Posts the requested message to the logging data queue.
|
||||
|
||||
@param message The message to send
|
||||
@return bool true if sending was successful.
|
||||
*/
|
||||
bool PostToLogQueue(santa_message_t *message);
|
||||
|
||||
/**
|
||||
Creates a new santa_message_t with some fields pre-filled.
|
||||
|
||||
@param credential The kauth_cred_t for this action, if available.
|
||||
If nullptr, will get the credential for the current process.
|
||||
*/
|
||||
static inline santa_message_t *NewMessage(kauth_cred_t credential) {
|
||||
bool should_release = false;
|
||||
if (credential == nullptr) {
|
||||
credential = kauth_cred_get_with_ref();
|
||||
should_release = true;
|
||||
}
|
||||
|
||||
auto message = new santa_message_t;
|
||||
message->uid = kauth_cred_getuid(credential);
|
||||
message->gid = kauth_cred_getgid(credential);
|
||||
message->pid = proc_selfpid();
|
||||
message->ppid = proc_selfppid();
|
||||
|
||||
if (should_release) {
|
||||
kauth_cred_unref(&credential);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current system uptime in microseconds
|
||||
*/
|
||||
static inline uint64_t GetCurrentUptime() {
|
||||
clock_sec_t sec;
|
||||
clock_usec_t usec;
|
||||
clock_get_system_microtime(&sec, &usec);
|
||||
return (uint64_t)((sec * 1000000) + usec);
|
||||
}
|
||||
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *root_decision_cache_;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *non_root_decision_cache_;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;
|
||||
SantaCache<pid_t, pid_t> *compiler_pid_set_;
|
||||
|
||||
SNTPrefixTree *filemod_prefix_filter_;
|
||||
|
||||
/**
|
||||
Return the correct cache for a given identifier.
|
||||
|
||||
@param identifier The identifier
|
||||
@return SantaCache* The cache to use
|
||||
*/
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *CacheForIdentifier(
|
||||
const santa_vnode_id_t identifier);
|
||||
|
||||
// This is the file system ID of the root filesystem,
|
||||
// used to determine which cache to use for requests
|
||||
uint64_t root_fsid_;
|
||||
|
||||
lck_grp_t *sdm_lock_grp_;
|
||||
lck_grp_attr_t *sdm_lock_grp_attr_;
|
||||
lck_attr_t *sdm_lock_attr_;
|
||||
|
||||
lck_mtx_t *decision_dataqueue_lock_;
|
||||
lck_mtx_t *log_dataqueue_lock_;
|
||||
|
||||
IOSharedDataQueue *decision_dataqueue_;
|
||||
IOSharedDataQueue *log_dataqueue_;
|
||||
uint32_t failed_decision_queue_requests_;
|
||||
uint32_t failed_log_queue_requests_;
|
||||
|
||||
int32_t listener_invocations_;
|
||||
int32_t pid_monitor_thread_count_ = 0;
|
||||
|
||||
pid_t client_pid_;
|
||||
|
||||
kauth_listener_t vnode_listener_;
|
||||
kauth_listener_t fileop_listener_;
|
||||
|
||||
struct timespec ts_ = {
|
||||
.tv_sec = kRequestLoopSleepMilliseconds / 1000,
|
||||
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000};
|
||||
};
|
||||
|
||||
/**
|
||||
The kauth callback function for the Vnode scope
|
||||
|
||||
@param credential actor's credentials
|
||||
@param idata data that was passed when the listener was registered
|
||||
@param action action that was requested
|
||||
@param arg0 VFS context
|
||||
@param arg1 Vnode being operated on
|
||||
@param arg2 Parent Vnode. May be nullptr.
|
||||
@param arg3 Pointer to an errno-style error.
|
||||
*/
|
||||
extern "C" int vnode_scope_callback(kauth_cred_t credential, void *idata,
|
||||
kauth_action_t action, uintptr_t arg0,
|
||||
uintptr_t arg1, uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
/**
|
||||
The kauth callback function for the FileOp scope
|
||||
|
||||
@param credential actor's credentials
|
||||
@param idata data that was passed when the listener was registered
|
||||
@param action action that was requested
|
||||
@param arg0 depends on action, usually the vnode ref.
|
||||
@param arg1 depends on action.
|
||||
@param arg2 depends on action, usually 0.
|
||||
@param arg3 depends on action, usually 0.
|
||||
*/
|
||||
extern "C" int fileop_scope_callback(kauth_cred_t credential, void *idata,
|
||||
kauth_action_t action, uintptr_t arg0,
|
||||
uintptr_t arg1, uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
@@ -1,55 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/santa_driver/SantaDriver.h"
|
||||
|
||||
#define super IOService
|
||||
#define SantaDriver com_google_SantaDriver
|
||||
|
||||
// The defines above can'be used in this function, we must use the full names.
|
||||
OSDefineMetaClassAndStructors(com_google_SantaDriver, IOService);
|
||||
|
||||
bool SantaDriver::start(IOService *provider) {
|
||||
if (!super::start(provider)) return false;
|
||||
|
||||
santaDecisionManager = new SantaDecisionManager;
|
||||
if (!santaDecisionManager->init() ||
|
||||
santaDecisionManager->StartListener() != kIOReturnSuccess) {
|
||||
santaDecisionManager->release();
|
||||
santaDecisionManager = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
registerService();
|
||||
|
||||
LOGI("Loaded, version %s.", OSKextGetCurrentVersionString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDriver::stop(IOService *provider) {
|
||||
santaDecisionManager->StopListener();
|
||||
santaDecisionManager->release();
|
||||
santaDecisionManager = nullptr;
|
||||
|
||||
LOGI("Unloaded.");
|
||||
|
||||
super::stop(provider);
|
||||
}
|
||||
|
||||
SantaDecisionManager *SantaDriver::GetDecisionManager() const {
|
||||
return santaDecisionManager;
|
||||
}
|
||||
|
||||
#undef super
|
||||
@@ -1,46 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTADRIVER_H
|
||||
#define SANTA__SANTA_DRIVER__SANTADRIVER_H
|
||||
|
||||
#include <IOKit/IOService.h>
|
||||
#include <libkern/OSKextLib.h>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/santa_driver/SantaDecisionManager.h"
|
||||
|
||||
///
|
||||
/// The driver class, which provides the start/stop functions and holds
|
||||
/// the SantaDecisionManager instance which the connected client
|
||||
/// communicates with.
|
||||
///
|
||||
class com_google_SantaDriver : public IOService {
|
||||
OSDeclareDefaultStructors(com_google_SantaDriver);
|
||||
|
||||
public:
|
||||
/// Called by the kernel when the kext is loaded
|
||||
bool start(IOService *provider) override;
|
||||
|
||||
/// Called by the kernel when the kext is unloaded
|
||||
void stop(IOService *provider) override;
|
||||
|
||||
/// Returns a pointer to the SantaDecisionManager created in start().
|
||||
SantaDecisionManager *GetDecisionManager() const;
|
||||
|
||||
private:
|
||||
SantaDecisionManager *santaDecisionManager;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADRIVER_H
|
||||
@@ -1,305 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/santa_driver/SantaDriverClient.h"
|
||||
|
||||
#define super IOUserClient
|
||||
#define SantaDriverClient com_google_SantaDriverClient
|
||||
|
||||
// The defines above can'be used in this function, must use the full names.
|
||||
OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient);
|
||||
|
||||
#pragma mark Driver Management
|
||||
|
||||
bool SantaDriverClient::initWithTask(
|
||||
task_t owningTask, void *securityID, UInt32 type) {
|
||||
if (clientHasPrivilege(
|
||||
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
|
||||
LOGW("Unprivileged client attempted to connect.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!super::initWithTask(owningTask, securityID, type)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SantaDriverClient::start(IOService *provider) {
|
||||
myProvider = OSDynamicCast(com_google_SantaDriver, provider);
|
||||
if (!myProvider) return false;
|
||||
|
||||
decisionManager = myProvider->GetDecisionManager();
|
||||
if (!decisionManager) return false;
|
||||
decisionManager->retain();
|
||||
|
||||
return super::start(provider);
|
||||
}
|
||||
|
||||
void SantaDriverClient::stop(IOService *provider) {
|
||||
myProvider = nullptr;
|
||||
decisionManager->release();
|
||||
decisionManager = nullptr;
|
||||
super::stop(provider);
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientDied() {
|
||||
LOGI("Client died.");
|
||||
decisionManager->DisconnectClient(true);
|
||||
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientClose() {
|
||||
LOGI("Client disconnected.");
|
||||
decisionManager->DisconnectClient();
|
||||
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
|
||||
}
|
||||
|
||||
bool SantaDriverClient::didTerminate(IOService *provider, IOOptionBits options, bool *defer) {
|
||||
decisionManager->DisconnectClient(false, 0);
|
||||
if (myProvider && myProvider->isOpen(this)) myProvider->close(this);
|
||||
return super::didTerminate(provider, options, defer);
|
||||
}
|
||||
|
||||
#pragma mark Fetching memory and data queue notifications
|
||||
|
||||
IOReturn SantaDriverClient::registerNotificationPort(
|
||||
mach_port_t port, UInt32 type, UInt32 ref) {
|
||||
if (port == MACH_PORT_NULL) return kIOReturnError;
|
||||
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION:
|
||||
decisionManager->SetDecisionPort(port);
|
||||
break;
|
||||
case QUEUETYPE_LOG:
|
||||
decisionManager->SetLogPort(port);
|
||||
break;
|
||||
default:
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientMemoryForType(
|
||||
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) {
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION:
|
||||
*options = 0;
|
||||
*memory = decisionManager->GetDecisionMemoryDescriptor();
|
||||
decisionManager->ConnectClient(proc_selfpid());
|
||||
break;
|
||||
case QUEUETYPE_LOG:
|
||||
*options = 0;
|
||||
*memory = decisionManager->GetLogMemoryDescriptor();
|
||||
break;
|
||||
default:
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
(*memory)->retain();
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
#pragma mark Callable Methods
|
||||
|
||||
IOReturn SantaDriverClient::open(
|
||||
OSObject *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (me->isInactive()) return kIOReturnNotAttached;
|
||||
if (!me->myProvider->open(me)) {
|
||||
LOGW("A second client tried to connect.");
|
||||
return kIOReturnExclusiveAccess;
|
||||
}
|
||||
|
||||
LOGI("Client connected.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::allow_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::allow_compiler(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW_COMPILER);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::deny_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_DENY);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::acknowledge_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ACK);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clear_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
const bool non_root_only = static_cast<const bool>(arguments->scalarInput[0]);
|
||||
me->decisionManager->ClearCache(non_root_only);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::remove_cache_entry(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->RemoveFromCache(*vnode_id);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::cache_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
arguments->scalarOutput[0] = me->decisionManager->RootCacheCount();
|
||||
arguments->scalarOutput[1] = me->decisionManager->NonRootCacheCount();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::check_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(*vnode_id);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::cache_bucket_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
santa_bucket_count_t *counts = reinterpret_cast<santa_bucket_count_t *>(
|
||||
arguments->structureOutput);
|
||||
const santa_bucket_count_t *input = reinterpret_cast<const santa_bucket_count_t *>(
|
||||
arguments->structureInput);
|
||||
|
||||
uint16_t s = sizeof(counts->per_bucket) / sizeof(uint16_t);
|
||||
counts->start = input->start;
|
||||
me->decisionManager->CacheBucketCount(counts->per_bucket, &s, &(counts->start));
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::filemod_prefix_filter_add(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
const char *prefix = reinterpret_cast<const char *>(arguments->structureInput);
|
||||
return me->decisionManager->FilemodPrefixFilterAdd(prefix, arguments->scalarOutput);
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::filemod_prefix_filter_reset(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
me->decisionManager->FilemodPrefixFilterReset();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
#pragma mark Method Resolution
|
||||
|
||||
IOReturn SantaDriverClient::externalMethod(
|
||||
UInt32 selector,
|
||||
IOExternalMethodArguments *arguments,
|
||||
IOExternalMethodDispatch *dispatch,
|
||||
OSObject *target,
|
||||
void *reference) {
|
||||
/// Array of methods callable by clients. The order of these must match the
|
||||
/// order of the items in SantaDriverMethods in SNTKernelCommon.h
|
||||
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
|
||||
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
|
||||
{ &SantaDriverClient::open, 0, 0, 0, 0 },
|
||||
{ &SantaDriverClient::allow_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::allow_compiler, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::deny_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::acknowledge_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
|
||||
{ &SantaDriverClient::remove_cache_entry, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
|
||||
{ &SantaDriverClient::check_cache, 0, sizeof(santa_vnode_id_t), 1, 0 },
|
||||
{ &SantaDriverClient::cache_bucket_count, 0, sizeof(santa_bucket_count_t),
|
||||
0, sizeof(santa_bucket_count_t) },
|
||||
{ &SantaDriverClient::filemod_prefix_filter_add, 0, sizeof(const char[MAXPATHLEN]), 1, 0 },
|
||||
{ &SantaDriverClient::filemod_prefix_filter_reset, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
if (selector >= static_cast<UInt32>(kSantaUserClientNMethods)) {
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
dispatch = &(sMethods[selector]);
|
||||
if (!target) target = this;
|
||||
return super::externalMethod(selector, arguments, dispatch, target, reference);
|
||||
}
|
||||
|
||||
#undef super
|
||||
@@ -1,136 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
#define SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
|
||||
#include <IOKit/IOUserClient.h>
|
||||
#include <sys/kauth.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/santa_driver/SantaDecisionManager.h"
|
||||
#include "Source/santa_driver/SantaDriver.h"
|
||||
|
||||
///
|
||||
/// This class is instantiated by IOKit when a new client process attempts to
|
||||
/// connect to the driver. Starting, stopping, handling connections, allocating
|
||||
/// shared memory and establishing a data queue is handled here.
|
||||
///
|
||||
/// Documentation on how the IOUserClient parts of this code work can be found
|
||||
/// here:
|
||||
/// https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html
|
||||
/// https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingDeviceDriver/WritingDeviceDriver.pdf
|
||||
///
|
||||
class com_google_SantaDriverClient : public IOUserClient {
|
||||
OSDeclareDefaultStructors(com_google_SantaDriverClient);
|
||||
|
||||
public:
|
||||
/// Called as part of IOServiceOpen in clients
|
||||
bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;
|
||||
|
||||
/// Called after initWithTask as part of IOServiceOpen
|
||||
bool start(IOService *provider) override;
|
||||
|
||||
/// Called when this class is stopping
|
||||
void stop(IOService *provider) override;
|
||||
|
||||
/// Called when a client manually disconnects (via IOServiceClose)
|
||||
IOReturn clientClose() override;
|
||||
|
||||
/// Called when a client dies
|
||||
IOReturn clientDied() override;
|
||||
|
||||
/// Called during termination
|
||||
bool didTerminate(IOService *provider, IOOptionBits options,
|
||||
bool *defer) override;
|
||||
|
||||
/// Called in clients with IOConnectSetNotificationPort
|
||||
IOReturn registerNotificationPort(mach_port_t port, UInt32 type,
|
||||
UInt32 refCon) override;
|
||||
|
||||
/// Called in clients with IOConnectMapMemory
|
||||
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options,
|
||||
IOMemoryDescriptor **memory) override;
|
||||
|
||||
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
|
||||
/// to the requested selector using the SantaDriverMethods enum in
|
||||
/// SNTKernelCommon.
|
||||
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments,
|
||||
IOExternalMethodDispatch *dispatch, OSObject *target,
|
||||
void *reference) override;
|
||||
|
||||
///
|
||||
/// The userspace callable methods are below. Each method corresponds
|
||||
/// to an entry in SantaDriverMethods.
|
||||
///
|
||||
|
||||
/// Called during client connection.
|
||||
static IOReturn open(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a binary.
|
||||
static IOReturn allow_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a compiler binary.
|
||||
static IOReturn allow_compiler(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to deny a binary.
|
||||
static IOReturn deny_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to acknowledge a binary request. This is used for
|
||||
/// large binaries that may take a while to reach a decision.
|
||||
static IOReturn acknowledge_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to empty the cache.
|
||||
static IOReturn clear_cache(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon call this to remove a single cache entry.
|
||||
static IOReturn remove_cache_entry(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in the cache
|
||||
static IOReturn cache_count(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out the status of a vnode_id in the cache.
|
||||
/// Output will be a santa_action_t.
|
||||
static IOReturn check_cache(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in each cache
|
||||
/// bucket. Input and output are both an instance of santa_bucket_count_t.
|
||||
static IOReturn cache_bucket_count(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to add filemod prefix filters at daemon startup.
|
||||
static IOReturn filemod_prefix_filter_add(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to reset the prefix filter tree.
|
||||
static IOReturn filemod_prefix_filter_reset(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
private:
|
||||
com_google_SantaDriver *myProvider;
|
||||
SantaDecisionManager *decisionManager;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
@@ -1,878 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <IOKit/IODataQueueClient.h>
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#import <IOKit/kext/KextManager.h>
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#include <libkern/OSKextLib.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
|
||||
///
|
||||
/// Kernel Extension Tests
|
||||
///
|
||||
/// Build and launch as root. This target is dependent on the santa-driver target and these
|
||||
/// tests will load santa-driver from the same location this binary is executed from, unloading
|
||||
/// any existing driver (and daemon) if necessary.
|
||||
///
|
||||
|
||||
#define TSTART(testName) \
|
||||
do { \
|
||||
printf(" %-50s ", testName); \
|
||||
} while (0)
|
||||
#define TPASS() \
|
||||
do { \
|
||||
printf("PASS\n"); \
|
||||
} while (0)
|
||||
#define TPASSINFO(fmt, ...) \
|
||||
do { \
|
||||
printf("PASS\n " fmt "\n", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#define TFAIL() \
|
||||
do { \
|
||||
printf("FAIL\n"); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
#define TFAILINFO(fmt, ...) \
|
||||
do { \
|
||||
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
@interface SantaKernelTests : NSObject
|
||||
@property io_connect_t connection;
|
||||
|
||||
// A block that tests can set to handle specific files/binaries.
|
||||
// The block should return an action to respond to the kernel with.
|
||||
// If no block is specified or no action is returned, the exec will be allowed.
|
||||
@property(atomic, copy) santa_action_t (^handlerBlock)(santa_message_t msg);
|
||||
|
||||
- (void)unloadDaemon;
|
||||
- (void)unloadExtension;
|
||||
- (void)loadExtension;
|
||||
- (void)runTests;
|
||||
@end
|
||||
|
||||
@implementation SantaKernelTests
|
||||
|
||||
#pragma mark - Test Helpers
|
||||
|
||||
/// Return an initialized NSTask for |path| with stdout, stdin and stderr directed to /dev/null
|
||||
- (NSTask *)taskWithPath:(NSString *)path {
|
||||
NSTask *t = [[NSTask alloc] init];
|
||||
t.launchPath = path;
|
||||
t.standardInput = nil;
|
||||
t.standardOutput = nil;
|
||||
t.standardError = nil;
|
||||
return t;
|
||||
}
|
||||
|
||||
- (NSString *)sha256ForPath:(NSString *)path {
|
||||
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
|
||||
NSData *fData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:nil];
|
||||
CC_SHA256([fData bytes], (unsigned int)[fData length], sha256);
|
||||
char buf[CC_SHA256_DIGEST_LENGTH * 2 + 1];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
|
||||
snprintf(buf + (2 * i), 4, "%02x", (unsigned char)sha256[i]);
|
||||
}
|
||||
buf[CC_SHA256_DIGEST_LENGTH * 2] = '\0';
|
||||
return @(buf);
|
||||
}
|
||||
|
||||
/// Return the path to the version of ld being used by clang.
|
||||
- (NSString *)ldPath {
|
||||
static NSString *path;
|
||||
if (!path) {
|
||||
NSTask *xcrun = [self taskWithPath:@"/usr/bin/xcrun"];
|
||||
xcrun.arguments = @[ @"-f", @"ld" ];
|
||||
xcrun.standardOutput = [NSPipe pipe];
|
||||
@try {
|
||||
[xcrun launch];
|
||||
[xcrun waitUntilExit];
|
||||
} @catch (NSException *exception) {
|
||||
return nil;
|
||||
}
|
||||
if (xcrun.terminationStatus != 0) return nil;
|
||||
NSData *data = [[xcrun.standardOutput fileHandleForReading] readDataToEndOfFile];
|
||||
if (!data) return nil;
|
||||
path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
|
||||
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
#pragma mark - Driver Helpers
|
||||
|
||||
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
|
||||
/// passing the |vnodeID|.
|
||||
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeid {
|
||||
switch (action) {
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_DENY:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ACK:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
default:
|
||||
TFAILINFO("postToKernelAction:forVnodeID: received unknown action type: %d", action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Call in-kernel function: |kSantaUserClientClearCache|
|
||||
- (void)flushCache {
|
||||
uint64_t nonRootOnly = 0;
|
||||
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, &nonRootOnly, 1, 0, 0);
|
||||
}
|
||||
|
||||
#pragma mark - Connection Tests
|
||||
|
||||
/// Tests the process of locating, attaching and opening the driver. Also verifies that the
|
||||
/// driver correctly refuses non-privileged connections.
|
||||
- (void)connectionTests {
|
||||
kern_return_t kr;
|
||||
io_service_t serviceObject;
|
||||
CFDictionaryRef classToMatch;
|
||||
|
||||
TSTART("Creates matching service dictionary");
|
||||
if (!(classToMatch = IOServiceMatching(USERCLIENT_CLASS))) {
|
||||
TFAIL();
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Locates Santa driver");
|
||||
serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch);
|
||||
if (!serviceObject) {
|
||||
TFAILINFO("Is santa-driver.kext loaded?");
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Driver refuses non-privileged connections");
|
||||
(void)setegid(-2);
|
||||
(void)seteuid(-2);
|
||||
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
|
||||
if (kr != kIOReturnBadArgument) {
|
||||
TFAIL();
|
||||
}
|
||||
(void)setegid(0);
|
||||
(void)seteuid(0);
|
||||
TPASS();
|
||||
|
||||
TSTART("Attaches to and starts Santa service");
|
||||
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
|
||||
IOObjectRelease(serviceObject);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
TFAILINFO("KR: %d", kr);
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Calls 'open' method on driver");
|
||||
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
if (kr == kIOReturnExclusiveAccess) {
|
||||
TFAILINFO("A client is already connected to the driver.\n"
|
||||
"Please kill the existing client and re-run the test.");
|
||||
} else if (kr != kIOReturnSuccess) {
|
||||
TFAILINFO("KR: %d", kr);
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Refuses second client");
|
||||
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
if (kr != kIOReturnExclusiveAccess) {
|
||||
TFAIL();
|
||||
}
|
||||
TPASS();
|
||||
}
|
||||
|
||||
#pragma mark - Listener
|
||||
|
||||
/// Tests the process of allocating & registering a notification port and mapping shared memory.
|
||||
/// From then on, monitors the IODataQueue and responds for files specifically used in other tests.
|
||||
/// For everything else, allows execution normally to avoid deadlocking the system.
|
||||
- (void)beginListening {
|
||||
TSTART("Allocates a notification port");
|
||||
mach_port_t receivePort;
|
||||
if (!(receivePort = IODataQueueAllocateNotificationPort())) {
|
||||
TFAIL();
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Registers the notification port");
|
||||
kern_return_t kr =
|
||||
IOConnectSetNotificationPort(self.connection, QUEUETYPE_DECISION, receivePort, 0);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
TFAILINFO("KR: %d", kr);
|
||||
return;
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Maps shared memory");
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(), &address, &size,
|
||||
kIOMapAnywhere);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
TFAILINFO("KR: %d", kr);
|
||||
}
|
||||
TPASS();
|
||||
|
||||
/// Begin listening for events
|
||||
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
|
||||
do {
|
||||
while (IODataQueueDataAvailable(queueMemory)) {
|
||||
santa_message_t vdata;
|
||||
UInt32 dataSize = sizeof(vdata);
|
||||
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
TFAILINFO("Error receiving data: %d", kr);
|
||||
continue;
|
||||
}
|
||||
if (vdata.action != ACTION_REQUEST_BINARY) continue;
|
||||
|
||||
santa_action_t action = ACTION_RESPOND_ALLOW;
|
||||
|
||||
@synchronized(self) {
|
||||
if (self.handlerBlock) action = self.handlerBlock(vdata);
|
||||
}
|
||||
|
||||
[self postToKernelAction:action forVnodeID:vdata.vnode_id];
|
||||
}
|
||||
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
|
||||
|
||||
IOConnectUnmapMemory(self.connection, kIODefaultMemoryType, mach_task_self(), address);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
}
|
||||
|
||||
#pragma mark - Functional Tests
|
||||
|
||||
- (void)receiveAndBlockTests {
|
||||
TSTART("Blocks denied binaries");
|
||||
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/ed", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
NSTask *ed = [self taskWithPath:@"/bin/ed"];
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAIL();
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)receiveAndCacheTests {
|
||||
TSTART("Permits & caches allowed binaries");
|
||||
|
||||
__block int timesSeenLs = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/ls", msg.path, 7) == 0) ++timesSeenLs;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
NSTask *ls = [self taskWithPath:@"/bin/ls"];
|
||||
[ls launch];
|
||||
[ls waitUntilExit];
|
||||
|
||||
if (timesSeenLs != 1) {
|
||||
TFAILINFO("Didn't record first run of ls");
|
||||
}
|
||||
|
||||
ls = [self taskWithPath:@"/bin/ls"];
|
||||
[ls launch];
|
||||
[ls waitUntilExit];
|
||||
|
||||
if (timesSeenLs > 1) {
|
||||
TFAILINFO("Received request for ls a second time");
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)invalidatesCacheTests {
|
||||
TSTART("Invalidates cache for manually closed FDs");
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSString *target =
|
||||
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
__strong __typeof(weakSelf) self = weakSelf;
|
||||
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
// Copy the pwd binary to a new file
|
||||
if (![fm copyItemAtPath:@"/bin/pwd" toPath:target error:nil]) {
|
||||
TFAILINFO("Failed to create temp file");
|
||||
}
|
||||
|
||||
// Launch the new file to put it in the cache
|
||||
NSTask *pwd = [self taskWithPath:target];
|
||||
[pwd launch];
|
||||
[pwd waitUntilExit];
|
||||
|
||||
// Exit if this fails with a useful message.
|
||||
if ([pwd terminationStatus] != 0) {
|
||||
TFAILINFO("First launch of test binary failed");
|
||||
}
|
||||
|
||||
// Now replace the contents of the test file (which is cached) with the contents of /bin/ed,
|
||||
// which is blocked by SHA-256 during the tests.
|
||||
FILE *infile = fopen("/bin/ed", "r");
|
||||
FILE *outfile = fopen(target.UTF8String, "w");
|
||||
int ch;
|
||||
while ((ch = fgetc(infile)) != EOF) {
|
||||
fputc(ch, outfile);
|
||||
}
|
||||
fclose(infile);
|
||||
|
||||
// Now try running the temp file again. If it succeeds, the test failed.
|
||||
NSTask *ed = [self taskWithPath:target];
|
||||
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after write while file open");
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
} @catch (NSException *exception) {
|
||||
// This is a pass, but we have more to do.
|
||||
}
|
||||
|
||||
// Close the file to flush the write.
|
||||
fclose(outfile);
|
||||
|
||||
// And try running the temp file again. If it succeeds, the test failed.
|
||||
ed = [self taskWithPath:target];
|
||||
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after file closed");
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
} @finally {
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invalidatesCacheAutoCloseTest {
|
||||
TSTART("Invalidates cache for auto closed FDs");
|
||||
|
||||
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
__strong __typeof(weakSelf) self = weakSelf;
|
||||
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
// Create temporary file
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
|
||||
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
|
||||
TFAILINFO("Failed to create temp file");
|
||||
}
|
||||
|
||||
// Launch the new file to put it in the cache
|
||||
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
[pwd launch];
|
||||
[pwd waitUntilExit];
|
||||
if ([pwd terminationStatus] != 0) {
|
||||
TFAILINFO("Second launch of test binary failed");
|
||||
}
|
||||
|
||||
// Replace file contents using dd, which doesn't close FDs
|
||||
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
|
||||
NSTask *dd = [self taskWithPath:@"/bin/dd"];
|
||||
dd.arguments = @[
|
||||
@"if=/bin/ed", @"of=invalidacachetest_tmp", @"bs=1",
|
||||
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
|
||||
];
|
||||
[dd launch];
|
||||
[dd waitUntilExit];
|
||||
|
||||
// And try running the temp file again. If it succeeds, the test failed.
|
||||
NSTask *ed = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after file closed");
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
} @finally {
|
||||
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearCacheTests {
|
||||
TSTART("Can clear cache");
|
||||
|
||||
__block int timesSeenCat = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/cat", msg.path, 8) == 0) ++timesSeenCat;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
NSTask *cat = [self taskWithPath:@"/bin/cat"];
|
||||
[cat launch];
|
||||
[cat waitUntilExit];
|
||||
|
||||
if (timesSeenCat != 1) {
|
||||
TFAILINFO("Didn't record first run of cat");
|
||||
}
|
||||
|
||||
[self flushCache];
|
||||
|
||||
cat = [self taskWithPath:@"/bin/cat"];
|
||||
[cat launch];
|
||||
[cat waitUntilExit];
|
||||
|
||||
if (timesSeenCat != 2) {
|
||||
TFAIL();
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)blocksDeniedTracedBinaries {
|
||||
TSTART("Denies blocked processes running while traced");
|
||||
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/mv", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
TFAILINFO("Failed to fork");
|
||||
} else if (pid > 0) {
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0) != pid)
|
||||
; // handle EINTR
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
|
||||
TPASS();
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
TFAILINFO("Process was executed and is waiting for debugger");
|
||||
} else {
|
||||
TFAILINFO("Process did not exit with EPERM as expected");
|
||||
}
|
||||
} else if (pid == 0) {
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
ptrace(PT_TRACE_ME, 0, 0, 0);
|
||||
execl("/bin/mv", "mv", NULL);
|
||||
_exit(errno);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testCachePerformance {
|
||||
TSTART("Test cache performance...");
|
||||
|
||||
// Execute echo 100 times, saving the time taken for each run
|
||||
std::vector<double> times;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
NSTask *t = [[NSTask alloc] init];
|
||||
t.launchPath = @"/bin/echo";
|
||||
t.standardOutput = [NSPipe pipe];
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
[t launch];
|
||||
[t waitUntilExit];
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
|
||||
if (i > 5) times.push_back(duration);
|
||||
}
|
||||
|
||||
// Sort and remove first 10 and last 10 entries.
|
||||
std::sort(times.begin(), times.end());
|
||||
times.erase(times.begin(), times.begin() + 10);
|
||||
times.erase(times.end() - 10, times.end());
|
||||
|
||||
// Calculate mean
|
||||
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
|
||||
|
||||
// Calculate stdev
|
||||
double accum = 0.0;
|
||||
std::for_each(times.begin(), times.end(),
|
||||
[&](const double d) { accum += (d - mean) * (d - mean); });
|
||||
double stdev = sqrt(accum / (times.size() - 1));
|
||||
|
||||
if (mean > 80 || stdev > 10) {
|
||||
TFAILINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
|
||||
} else {
|
||||
TPASSINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testLargeBinary {
|
||||
TSTART("Handles large binary...");
|
||||
|
||||
__block int calCount = 0;
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
__strong __typeof(weakSelf) self = weakSelf;
|
||||
if (strncmp("/usr/bin/cal", msg.path, 12) == 0) {
|
||||
if (calCount++) TFAILINFO("Large binary should not re-request");
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
|
||||
dispatch_get_global_queue(0, 0), ^{
|
||||
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
|
||||
});
|
||||
return ACTION_RESPOND_ACK;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
@try {
|
||||
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
|
||||
[testexec launch];
|
||||
int sleepCount = 0;
|
||||
while ([testexec isRunning]) {
|
||||
sleep(1);
|
||||
if (++sleepCount > 5) TFAILINFO("Took longer than expected to start/stop");
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
TFAILINFO("Failed to launch");
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)testPendingTransitiveRules {
|
||||
TSTART("Adds pending transitive whitelist rules");
|
||||
|
||||
NSString *ldPath = [self ldPath];
|
||||
if (!ldPath) {
|
||||
TFAILINFO("Couldn't get path to ld");
|
||||
}
|
||||
|
||||
// Clear out cached decisions from any previous tests.
|
||||
[self flushCache];
|
||||
|
||||
__block int ldCount = 0;
|
||||
__block int helloCount = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (!strcmp(ldPath.UTF8String, msg.path)) {
|
||||
ldCount++;
|
||||
return ACTION_RESPOND_ALLOW_COMPILER;
|
||||
} else if (!strcmp("/private/tmp/hello", msg.path)) {
|
||||
helloCount++;
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
// Write source file to /tmp/hello.c
|
||||
FILE *out = fopen("/tmp/hello.c", "wb");
|
||||
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
|
||||
fclose(out);
|
||||
// Then compile it with clang and ld, the latter of which has been marked as a compiler.
|
||||
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
|
||||
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
|
||||
[clang launch];
|
||||
[clang waitUntilExit];
|
||||
// Make sure that our version of ld marked as compiler was run. This assumes that
|
||||
// "xcode-select -p" returns "/Applications/Xcode.app/Contents/Developer"
|
||||
if (ldCount != 1) {
|
||||
TFAILINFO("Didn't record run of ld");
|
||||
}
|
||||
// Check if we can now run /private/tmp/hello. If working correctly, there will already be
|
||||
// a pending transitive rule in the cache, so no decision request will be sent to listener.
|
||||
// If for some reason a decision request is sent, then binary will be denied.
|
||||
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
|
||||
@try {
|
||||
[hello launch];
|
||||
[hello waitUntilExit];
|
||||
} @catch (NSException *exception) {
|
||||
TFAILINFO("could not launch /private/tmp/hello: %s", exception.reason.UTF8String);
|
||||
}
|
||||
// Check that the listener was not consulted for the decision.
|
||||
if (helloCount > 0) {
|
||||
TFAILINFO("pending decision for /private/tmp/hello was not in cache");
|
||||
}
|
||||
|
||||
// Clean up
|
||||
remove("/tmp/hello");
|
||||
remove("/tmp/hello.c");
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)testNoTransitiveRules {
|
||||
TSTART("No transitive rule generated by non-compiler");
|
||||
|
||||
NSString *ldPath = [self ldPath];
|
||||
if (!ldPath) {
|
||||
TFAILINFO("Couldn't get path to ld");
|
||||
}
|
||||
|
||||
// Clear out cached decisions from any previous tests.
|
||||
[self flushCache];
|
||||
|
||||
__block int ldCount = 0;
|
||||
__block int helloCount = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (!strcmp(ldPath.UTF8String, msg.path)) {
|
||||
ldCount++;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
} else if (!strcmp("/private/tmp/hello", msg.path)) {
|
||||
helloCount++;
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
// Write source file to /tmp/hello.c
|
||||
FILE *out = fopen("/tmp/hello.c", "wb");
|
||||
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
|
||||
fclose(out);
|
||||
// Then compile it with clang and ld, neither of which have been marked as a compiler.
|
||||
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
|
||||
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
|
||||
@try {
|
||||
[clang launch];
|
||||
[clang waitUntilExit];
|
||||
} @catch (NSException *exception) {
|
||||
TFAILINFO("Couldn't launch clang");
|
||||
}
|
||||
// Make sure that our version of ld was run. This assumes that "xcode-select -p"
|
||||
// returns "/Applications/Xcode.app/Contents/Developer"
|
||||
if (ldCount != 1) {
|
||||
TFAILINFO("Didn't record run of ld");
|
||||
}
|
||||
// Check that we cannot run /private/tmp/hello.
|
||||
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
|
||||
@try {
|
||||
[hello launch];
|
||||
[hello waitUntilExit];
|
||||
TFAILINFO("Should not have been able to launch /private/tmp/hello");
|
||||
} @catch (NSException *exception) {
|
||||
// All good
|
||||
}
|
||||
// Check that there wasn't a decision for /private/tmp/hello in the cache.
|
||||
if (helloCount != 1) {
|
||||
TFAILINFO("decision for /private/tmp/hello found in cache");
|
||||
}
|
||||
|
||||
// Clean up
|
||||
remove("/tmp/hello");
|
||||
remove("/tmp/hello.c");
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)testFilemodPrefixFilter {
|
||||
TSTART("Testing filemod prefix filter");
|
||||
|
||||
NSString *filter =
|
||||
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
|
||||
@"are the same for all non-accelerating observers, and he showed that the speed of light "
|
||||
@"within a vacuum is the same no matter the speed at which an observer travels.";
|
||||
|
||||
// Create a buffer that has 1024 bytes of characters, non-terminating.
|
||||
char buffer[MAXPATHLEN + 1]; // +1 is for the null byte from strlcpy(). It falls off the ledge.
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (![filter getFileSystemRepresentation:buffer + (i * filter.length) maxLength:MAXPATHLEN]) {
|
||||
TFAILINFO("Invalid filemod prefix filter: %s", filter.UTF8String);
|
||||
}
|
||||
}
|
||||
strlcpy(&buffer[1016], "E = mc²", 9); // Fill in the last 8 bytes.
|
||||
|
||||
uint64_t n = 0;
|
||||
uint32_t n_len = 1;
|
||||
|
||||
// The filter should currently be empty. It is reset when the client disconnects.
|
||||
// Fill up the 1024 node capacity.
|
||||
kern_return_t ret =
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
|
||||
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
|
||||
if (ret != kIOReturnSuccess || n != 1024) {
|
||||
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
|
||||
}
|
||||
|
||||
// Make sure it will enforce capacity.
|
||||
const char *too_much = "B";
|
||||
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
|
||||
too_much, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
if (ret != kIOReturnNoResources || n != 1024) {
|
||||
TFAILINFO("Failed enforce capacity: got %llu nodes expected 1024", n);
|
||||
}
|
||||
|
||||
// Make sure it will prune.
|
||||
const char *ignore_it_all = "A";
|
||||
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
|
||||
ignore_it_all, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
// Expect 1 "A" node.
|
||||
if (ret != kIOReturnSuccess || n != 1) {
|
||||
TFAILINFO("Failed to prune the prefix filter: got %llu nodes expected 1", n);
|
||||
}
|
||||
|
||||
// Reset.
|
||||
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
|
||||
NULL, NULL);
|
||||
|
||||
// And fill it back up again.
|
||||
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
|
||||
buffer, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
|
||||
if (ret != kIOReturnSuccess || n != 1024) {
|
||||
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
#pragma mark - Main
|
||||
|
||||
- (void)unloadDaemon {
|
||||
NSTask *t = [[NSTask alloc] init];
|
||||
t.launchPath = @"/bin/launchctl";
|
||||
t.arguments = @[ @"remove", @"com.google.santad" ];
|
||||
t.standardOutput = t.standardError = [NSPipe pipe];
|
||||
[t launch];
|
||||
[t waitUntilExit];
|
||||
}
|
||||
|
||||
- (void)unloadExtension {
|
||||
// Don't check the status of this, the kext may not be loaded..
|
||||
OSStatus ret = KextManagerUnloadKextWithIdentifier(CFSTR("com.google.santa-driver"));
|
||||
if (ret != kOSReturnSuccess && ret != kOSKextReturnNotFound) {
|
||||
NSLog(@"Failed to unload extension: 0x%X", ret);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadExtension {
|
||||
TSTART("Loads extension");
|
||||
|
||||
NSError *error;
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
|
||||
NSString *src = [[fm currentDirectoryPath] stringByAppendingPathComponent:@"santa-driver.kext"];
|
||||
NSString *dest = [NSTemporaryDirectory() stringByAppendingPathComponent:@"santa-driver.kext"];
|
||||
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
|
||||
if (![fm copyItemAtPath:src toPath:dest error:&error] || error) {
|
||||
TFAILINFO("Failed to copy kext: %s", error.description.UTF8String);
|
||||
}
|
||||
|
||||
NSDictionary *attrs = @{
|
||||
NSFileOwnerAccountName : @"root",
|
||||
NSFileGroupOwnerAccountName : @"wheel",
|
||||
NSFilePosixPermissions : @0755
|
||||
};
|
||||
|
||||
[fm setAttributes:attrs ofItemAtPath:dest error:NULL];
|
||||
for (NSString *path in [fm enumeratorAtPath:dest]) {
|
||||
[fm setAttributes:attrs ofItemAtPath:[dest stringByAppendingPathComponent:path] error:NULL];
|
||||
}
|
||||
|
||||
NSURL *destURL = [NSURL fileURLWithPath:dest];
|
||||
OSStatus ret = KextManagerLoadKextWithURL((__bridge CFURLRef)destURL, NULL);
|
||||
if (ret != kOSReturnSuccess) {
|
||||
TFAILINFO("Failed to load kext: 0x%X", ret);
|
||||
}
|
||||
usleep(50000);
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)runTests {
|
||||
printf("-> Connection tests:\n");
|
||||
|
||||
// Test that connection can be established
|
||||
[self connectionTests];
|
||||
|
||||
// Open driver and begin listening for events. Run this on background thread
|
||||
// so we can continue running tests.
|
||||
[self performSelectorInBackground:@selector(beginListening) withObject:nil];
|
||||
|
||||
// Wait for driver to finish getting ready
|
||||
sleep(1);
|
||||
|
||||
printf("\n-> Functional tests:\n");
|
||||
[self receiveAndBlockTests];
|
||||
[self receiveAndCacheTests];
|
||||
[self invalidatesCacheTests];
|
||||
[self invalidatesCacheAutoCloseTest];
|
||||
[self clearCacheTests];
|
||||
[self blocksDeniedTracedBinaries];
|
||||
[self testLargeBinary];
|
||||
[self testPendingTransitiveRules];
|
||||
[self testNoTransitiveRules];
|
||||
[self testFilemodPrefixFilter];
|
||||
|
||||
printf("\n-> Performance tests:\n");
|
||||
[self testCachePerformance];
|
||||
|
||||
printf("\nAll tests passed.\n\n");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
@autoreleasepool {
|
||||
setbuf(stdout, NULL);
|
||||
|
||||
if (getuid() != 0) {
|
||||
printf("Please run as root\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SantaKernelTests *skt = [[SantaKernelTests alloc] init];
|
||||
printf("\nSanta Kernel Tests\n==================\n\n");
|
||||
printf("-> Loading tests:\n");
|
||||
[skt unloadDaemon];
|
||||
[skt unloadExtension];
|
||||
[skt loadExtension];
|
||||
printf("\n");
|
||||
|
||||
[skt runTests];
|
||||
[skt unloadExtension];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#include <mach/mach_types.h>
|
||||
|
||||
#define STR_EXPAND(tok) #tok
|
||||
#define STR(tok) STR_EXPAND(tok)
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern kern_return_t _start(kmod_info_t *ki, void *data);
|
||||
extern kern_return_t _stop(kmod_info_t *ki, void *data);
|
||||
|
||||
__attribute__((visibility("default"))) \
|
||||
KMOD_EXPLICIT_DECL(com.google.santa-driver, STR(SANTA_VERSION), _start, _stop)
|
||||
|
||||
__private_extern__ kmod_start_func_t *_realmain = 0;
|
||||
__private_extern__ kmod_stop_func_t *_antimain = 0;
|
||||
|
||||
__private_extern__ int _kext_apple_cc = __APPLE_CC__ ;
|
||||
}
|
||||
|
||||
#include <IOKit/IOService.h>
|
||||
#include <IOKit/IOUserClient.h>
|
||||
|
||||
// The macOS 10.15 SDK added these Dispatch methods but they aren't
|
||||
// available on older macOS versions and so prevent kext linking.
|
||||
// Defining them here as hidden weak no-op's fixes linking and seems to work.
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSMetaClassBase::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSObject::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOService::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOUserClient::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
@@ -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"],
|
||||
|
||||
@@ -3,7 +3,9 @@ load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santactl_lib",
|
||||
@@ -33,11 +35,11 @@ objc_library(
|
||||
sdk_frameworks = ["IOKit"],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTRule",
|
||||
@@ -46,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",
|
||||
],
|
||||
@@ -63,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"],
|
||||
@@ -86,6 +90,7 @@ santa_unit_test(
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
@@ -96,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");
|
||||
}
|
||||
|
||||
@@ -122,6 +123,26 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
[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;
|
||||
@@ -80,9 +74,9 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
|
||||
SNTConfigurator *configurator = [SNTConfigurator configurator];
|
||||
|
||||
BOOL cachingEnabled = (![configurator enableSystemExtension] || [configurator enableSysxCache]);
|
||||
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,22 +174,26 @@ 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),
|
||||
@"watchdog_ram_events" : @(ramEvents),
|
||||
@"watchdog_cpu_peak" : @(cpuPeak),
|
||||
@"watchdog_ram_peak" : @(ramPeak),
|
||||
@"block_usb" : @(configurator.blockUSBMount),
|
||||
@"remount_usb_mode" : (configurator.blockUSBMount && configurator.remountUSBMode.count
|
||||
? configurator.remountUSBMode
|
||||
: @""),
|
||||
},
|
||||
@"database" : @{
|
||||
@"binary_rules" : @(binaryRuleCount),
|
||||
@@ -196,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),
|
||||
@@ -219,16 +228,22 @@ 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 | %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");
|
||||
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
|
||||
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
|
||||
@@ -237,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
|
||||
|
||||
@@ -13,13 +13,12 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <IOKit/kext/KextManager.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@@ -50,7 +49,6 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
if ([arguments containsObject:@"--json"]) {
|
||||
NSDictionary *versions = @{
|
||||
@"santa-driver" : [self santaKextVersion],
|
||||
@"santad" : [self santadVersion],
|
||||
@"santactl" : [self santactlVersion],
|
||||
@"SantaGUI" : [self santaAppVersion],
|
||||
@@ -62,7 +60,6 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
encoding:NSUTF8StringEncoding];
|
||||
printf("%s\n", [versionsStr UTF8String]);
|
||||
} else {
|
||||
printf("%-15s | %s\n", "santa-driver", [[self santaKextVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santad", [[self santadVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santactl", [[self santactlVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "SantaGUI", [[self santaAppVersion] UTF8String]);
|
||||
@@ -70,38 +67,25 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
exit(0);
|
||||
}
|
||||
|
||||
- (NSString *)santaKextVersion {
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
return @"un-needed (SystemExtension being used)";
|
||||
}
|
||||
|
||||
NSDictionary *loadedKexts = CFBridgingRelease(KextManagerCopyLoadedKextInfo(
|
||||
(__bridge CFArrayRef) @[ @(USERCLIENT_ID) ], (__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
|
||||
|
||||
if (loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"]) {
|
||||
return loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
SNTFileInfo *driverInfo = [[SNTFileInfo alloc] initWithPath:@(kKextPath)];
|
||||
if (driverInfo) {
|
||||
return [driverInfo.bundleVersion stringByAppendingString:@" (unloaded)"];
|
||||
}
|
||||
|
||||
return @"not found";
|
||||
- (NSString *)composeVersionsFromDict:(NSDictionary *)dict {
|
||||
if (!dict[@"CFBundleVersion"]) return @"";
|
||||
NSString *productVersion = dict[@"CFBundleShortVersionString"];
|
||||
NSString *buildVersion = [[dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
|
||||
return [NSString stringWithFormat:@"%@ (build %@)", productVersion, buildVersion];
|
||||
}
|
||||
|
||||
- (NSString *)santadVersion {
|
||||
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaDPath)];
|
||||
return daemonInfo.bundleVersion ?: @"";
|
||||
return [self composeVersionsFromDict:daemonInfo.infoPlist];
|
||||
}
|
||||
|
||||
- (NSString *)santaAppVersion {
|
||||
SNTFileInfo *guiInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaAppPath)];
|
||||
return guiInfo.bundleVersion ?: @"";
|
||||
return [self composeVersionsFromDict:guiInfo.infoPlist];
|
||||
}
|
||||
|
||||
- (NSString *)santactlVersion {
|
||||
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] ?: @"";
|
||||
return [self composeVersionsFromDict:[[NSBundle mainBundle] infoDictionary]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
>>> 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
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_bundle")
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
objc_library(
|
||||
name = "santad_lib",
|
||||
name = "database_controller",
|
||||
srcs = [
|
||||
"DataLayer/SNTDatabaseTable.h",
|
||||
"DataLayer/SNTDatabaseTable.m",
|
||||
@@ -12,21 +16,161 @@ objc_library(
|
||||
"DataLayer/SNTEventTable.m",
|
||||
"DataLayer/SNTRuleTable.h",
|
||||
"DataLayer/SNTRuleTable.m",
|
||||
"SNTDatabaseController.h",
|
||||
"SNTDatabaseController.m",
|
||||
],
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTEventProvider",
|
||||
hdrs = ["EventProviders/SNTEventProvider.h"],
|
||||
deps = [
|
||||
"//Source/common:SNTCommon",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "endpoint_security_manager",
|
||||
srcs = [
|
||||
"EventProviders/SNTEndpointSecurityManager.h",
|
||||
"EventProviders/SNTEndpointSecurityManager.mm",
|
||||
],
|
||||
sdk_dylibs = [
|
||||
"EndpointSecurity",
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":SNTEventProvider",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "event_logs_common",
|
||||
srcs = [
|
||||
"DataLayer/SNTDatabaseTable.h",
|
||||
"DataLayer/SNTRuleTable.h",
|
||||
"Logs/SNTEventLog.h",
|
||||
"Logs/SNTEventLog.m",
|
||||
"Logs/SNTFileEventLog.h",
|
||||
"Logs/SNTProtobufEventLog.h",
|
||||
"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",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "protobuf_event_logs",
|
||||
srcs = [
|
||||
"Logs/SNTLogOutput.h",
|
||||
"Logs/SNTProtobufEventLog.h",
|
||||
"Logs/SNTProtobufEventLog.m",
|
||||
"Logs/SNTSimpleMaildir.h",
|
||||
"Logs/SNTSimpleMaildir.m",
|
||||
],
|
||||
deps = [
|
||||
":event_logs_common",
|
||||
"//Source/common:SNTAllowlistInfo",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:santa_objc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "syslog_event_logs",
|
||||
srcs = [
|
||||
"EventProviders/SNTEndpointSecurityManager.h",
|
||||
"EventProviders/SNTEventProvider.h",
|
||||
"Logs/SNTSyslogEventLog.h",
|
||||
"Logs/SNTSyslogEventLog.m",
|
||||
],
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "file_event_logs",
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santad_lib",
|
||||
srcs = [
|
||||
"DataLayer/SNTDatabaseTable.h",
|
||||
"DataLayer/SNTEventTable.h",
|
||||
"DataLayer/SNTRuleTable.h",
|
||||
"EventProviders/SNTCachingEndpointSecurityManager.h",
|
||||
"EventProviders/SNTCachingEndpointSecurityManager.mm",
|
||||
"EventProviders/SNTDeviceManager.h",
|
||||
"EventProviders/SNTDeviceManager.mm",
|
||||
"EventProviders/SNTDriverManager.h",
|
||||
"EventProviders/SNTDriverManager.m",
|
||||
"EventProviders/SNTEndpointSecurityManager.h",
|
||||
"EventProviders/SNTEndpointSecurityManager.mm",
|
||||
"EventProviders/SNTEventProvider.h",
|
||||
"Logs/SNTEventLog.h",
|
||||
"Logs/SNTEventLog.m",
|
||||
"Logs/SNTFileEventLog.h",
|
||||
"Logs/SNTFileEventLog.m",
|
||||
"Logs/SNTSyslogEventLog.h",
|
||||
"Logs/SNTSyslogEventLog.m",
|
||||
"SNTApplication.h",
|
||||
"SNTApplication.m",
|
||||
"SNTCompilerController.h",
|
||||
@@ -34,7 +178,6 @@ objc_library(
|
||||
"SNTDaemonControlController.h",
|
||||
"SNTDaemonControlController.m",
|
||||
"SNTDatabaseController.h",
|
||||
"SNTDatabaseController.m",
|
||||
"SNTExecutionController.h",
|
||||
"SNTExecutionController.m",
|
||||
"SNTNotificationQueue.h",
|
||||
@@ -54,25 +197,33 @@ 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:SNTKernelCommon",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//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/common:SNTXPCSyncServiceInterface",
|
||||
"//Source/common:SNTXPCUnprivilegedControlInterface",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/santad:SNTApplicationCoreMetrics",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
@@ -97,6 +248,9 @@ objc_library(
|
||||
"EventProviders/EndpointSecurityTestUtil.h",
|
||||
"EventProviders/EndpointSecurityTestUtil.mm",
|
||||
],
|
||||
hdrs = [
|
||||
"EventProviders/EndpointSecurityTestUtil.h",
|
||||
],
|
||||
sdk_dylibs = [
|
||||
"EndpointSecurity",
|
||||
"bsm",
|
||||
@@ -133,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"],
|
||||
@@ -148,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 = [
|
||||
@@ -158,17 +322,19 @@ santa_unit_test(
|
||||
":santad_lib",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"//Source/common:SNTXPCSyncdInterface",
|
||||
"//Source/common:SantaCache",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
"@OCMock",
|
||||
@@ -203,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",
|
||||
@@ -230,7 +400,9 @@ santa_unit_test(
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
@@ -239,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",
|
||||
@@ -250,7 +424,9 @@ santa_unit_test(
|
||||
":DiskArbitrationTestLib",
|
||||
":EndpointSecurityTestLib",
|
||||
":santad_lib",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
"@OCMock",
|
||||
@@ -260,7 +436,9 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTApplicationTest",
|
||||
srcs = [
|
||||
"SNTApplication.h",
|
||||
"SNTApplicationTest.m",
|
||||
"SNTDatabaseController.h",
|
||||
],
|
||||
data = [
|
||||
"//Source/santad/testdata:binaryrules_testdata",
|
||||
@@ -270,9 +448,13 @@ santa_unit_test(
|
||||
"EndpointSecurity",
|
||||
"bsm",
|
||||
],
|
||||
tags = ["exclusive"],
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
":santad_lib",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
"@OCMock",
|
||||
@@ -310,6 +492,7 @@ santa_unit_test(
|
||||
deps = [
|
||||
":SNTApplicationCoreMetrics",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTSystemInfo",
|
||||
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
|
||||
@@ -317,14 +500,39 @@ 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:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:santa_objc_proto",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":SNTApplicationCoreMetricsTest",
|
||||
":SNTApplicationTest",
|
||||
":SNTDeviceManagerTest",
|
||||
":SNTEndpointSecurityManagerTest",
|
||||
":SNTEventTableTest",
|
||||
":SNTExecutionControllerTest",
|
||||
":SNTProtobufEventLogTest",
|
||||
":SNTRuleTableTest",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
CF_EXTERN_C_BEGIN
|
||||
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);
|
||||
CF_EXTERN_C_END
|
||||
|
||||
@class ESMessage;
|
||||
@@ -64,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,
|
||||
|
||||
@@ -26,6 +26,36 @@ es_string_token_t MakeStringToken(const NSString *_Nonnull s) {
|
||||
.data = [s UTF8String],
|
||||
};
|
||||
}
|
||||
|
||||
es_file_t MakeESFile(const char *path) {
|
||||
es_file_t esFile = {};
|
||||
|
||||
esFile.path.data = path;
|
||||
esFile.path.length = strlen(path);
|
||||
esFile.path_truncated = false;
|
||||
|
||||
// Note: stat info is currently unused / not populated
|
||||
|
||||
return esFile;
|
||||
}
|
||||
|
||||
es_process_t MakeESProcess(es_file_t *esFile) {
|
||||
es_process_t esProc = {};
|
||||
esProc.executable = esFile;
|
||||
return esProc;
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator,
|
||||
struct timespec ts) {
|
||||
es_message_t esMsg = {};
|
||||
|
||||
esMsg.time = ts;
|
||||
esMsg.event_type = eventType;
|
||||
esMsg.process = instigator;
|
||||
|
||||
return esMsg;
|
||||
}
|
||||
|
||||
CF_EXTERN_C_END
|
||||
|
||||
@implementation ESMessage
|
||||
@@ -176,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]) {
|
||||
@@ -204,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;
|
||||
@@ -252,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
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
#include "Source/common/SNTDeviceEvent.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^SNTDeviceBlockCallback)(SNTDeviceEvent *event);
|
||||
|
||||
/*
|
||||
* Manages DiskArbitration and EndpointSecurity to monitor/block/remount USB
|
||||
* storage devices.
|
||||
@@ -25,9 +31,12 @@
|
||||
@property(nonatomic, readwrite) BOOL subscribed;
|
||||
@property(nonatomic, readwrite) BOOL blockUSBMount;
|
||||
@property(nonatomic, readwrite, nullable) NSArray<NSString *> *remountArgs;
|
||||
@property(nonatomic, nullable) SNTDeviceBlockCallback deviceBlockCallback;
|
||||
|
||||
- (instancetype _Nonnull)init;
|
||||
- (instancetype)init;
|
||||
- (void)listen;
|
||||
- (BOOL)subscribed;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/santad/Logs/SNTEventLog.h"
|
||||
|
||||
@@ -105,6 +106,8 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SNTDeviceManager ()
|
||||
|
||||
@property DASessionRef diskArbSession;
|
||||
@@ -115,7 +118,7 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
|
||||
@implementation SNTDeviceManager
|
||||
|
||||
- (instancetype _Nonnull)init API_AVAILABLE(macos(10.15)) {
|
||||
- (instancetype)init API_AVAILABLE(macos(10.15)) {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_blockUSBMount = false;
|
||||
@@ -167,6 +170,7 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
|
||||
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));
|
||||
@@ -196,41 +200,84 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
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: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];
|
||||
}
|
||||
|
||||
if (self.deviceBlockCallback) {
|
||||
self.deviceBlockCallback(event);
|
||||
}
|
||||
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
}
|
||||
|
||||
@@ -257,26 +304,34 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
@@ -284,17 +339,20 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
- (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;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <sys/mount.h>
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santad/EventProviders/SNTDeviceManager.h"
|
||||
|
||||
#import "Source/santad/EventProviders/DiskArbitrationTestUtil.h"
|
||||
@@ -39,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
|
||||
@@ -83,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;
|
||||
@@ -117,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);
|
||||
}
|
||||
|
||||
@@ -132,10 +153,32 @@
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
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_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, YES);
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:60.0];
|
||||
|
||||
XCTAssertEqualObjects(gotRemountedArgs, deviceManager.remountArgs);
|
||||
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testBlockNoRemount {
|
||||
@@ -148,10 +191,101 @@
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
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_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, NO);
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:60.0];
|
||||
|
||||
XCTAssertNil(gotRemountedArgs);
|
||||
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
|
||||
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
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
@class SNTNotificationMessage;
|
||||
|
||||
///
|
||||
/// Manages the connection between daemon and kernel.
|
||||
///
|
||||
@interface SNTDriverManager : NSObject <SNTEventProvider>
|
||||
|
||||
///
|
||||
/// Handles locating and connecting to the driver. If driver is not loaded, will
|
||||
/// sleep until it is. If driver is loaded but connection fails (like if a client is
|
||||
/// already connected), will return nil.
|
||||
///
|
||||
- (instancetype)init;
|
||||
|
||||
///
|
||||
/// Unloads the driver.
|
||||
///
|
||||
+ (void)unloadDriver;
|
||||
|
||||
///
|
||||
/// Handles requests from the kernel using the given block.
|
||||
/// @note Loops indefinitely unless there is an error trying to read data from the data queue.
|
||||
///
|
||||
- (void)listenForDecisionRequests:(void (^)(santa_message_t message))callback;
|
||||
|
||||
///
|
||||
/// Handles requests from the kernel using the given block.
|
||||
/// @note Loops indefinitely unless there is an error trying to read data from the data queue.
|
||||
///
|
||||
- (void)listenForLogRequests:(void (^)(santa_message_t message))callback;
|
||||
|
||||
///
|
||||
/// Sends a response to a query back to the kernel.
|
||||
///
|
||||
- (kern_return_t)postAction:(santa_action_t)action forMessage:(santa_message_t)sm;
|
||||
|
||||
///
|
||||
/// Get the number of binaries in the kernel's caches.
|
||||
///
|
||||
- (NSArray<NSNumber *> *)cacheCounts;
|
||||
|
||||
///
|
||||
/// Return an array representing all buckets in the kernel's decision cache where each number
|
||||
/// is the number of items in that bucket.
|
||||
///
|
||||
- (NSArray<NSNumber *> *)cacheBucketCount;
|
||||
|
||||
///
|
||||
/// Flush the kernel's binary cache.
|
||||
///
|
||||
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly;
|
||||
|
||||
///
|
||||
/// Check the kernel cache for a VnodeID.
|
||||
///
|
||||
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID;
|
||||
|
||||
///
|
||||
/// Returns whether the connection to the driver has been established.
|
||||
///
|
||||
@property(readonly) BOOL connectionEstablished;
|
||||
|
||||
///
|
||||
/// Remove single entry from the kernel cache for given VnodeID.
|
||||
///
|
||||
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId;
|
||||
|
||||
///
|
||||
/// Adds in-kernel prefix filters for file modification logs.
|
||||
///
|
||||
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters;
|
||||
|
||||
///
|
||||
/// Resets the filter.
|
||||
///
|
||||
- (void)fileModificationPrefixFilterReset;
|
||||
|
||||
@end
|
||||
@@ -1,314 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/EventProviders/SNTDriverManager.h"
|
||||
|
||||
#import <IOKit/IODataQueueClient.h>
|
||||
#import <IOKit/kext/KextManager.h>
|
||||
|
||||
#include <mach/mach_port.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
@interface SNTDriverManager ()
|
||||
@property io_connect_t connection;
|
||||
@property(readwrite) BOOL connectionEstablished;
|
||||
@property(nonatomic, readonly) dispatch_queue_t dmAuthQueue;
|
||||
@property(nonatomic, readonly) dispatch_queue_t dmNotifiyQueue;
|
||||
@end
|
||||
|
||||
@implementation SNTDriverManager
|
||||
|
||||
#pragma mark init/dealloc
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_dmAuthQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.dm_auth", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(_dmAuthQueue,
|
||||
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0));
|
||||
_dmNotifiyQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.dm_notify", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(_dmNotifiyQueue, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
|
||||
|
||||
CFDictionaryRef classToMatch = IOServiceMatching(USERCLIENT_CLASS);
|
||||
if (!classToMatch) {
|
||||
LOGE(@"Failed to create matching dictionary");
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Attempt to load driver. It may already be running, so ignore any return value.
|
||||
KextManagerLoadKextWithIdentifier(CFSTR(USERCLIENT_ID), NULL);
|
||||
|
||||
// Wait for the driver to appear
|
||||
[self waitForDriver:classToMatch];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
IOServiceClose(_connection);
|
||||
}
|
||||
|
||||
+ (void)unloadDriver {
|
||||
KextManagerUnloadKextWithIdentifier(CFSTR(USERCLIENT_ID));
|
||||
}
|
||||
|
||||
#pragma mark Driver Waiting
|
||||
|
||||
// Helper function used with IOServiceAddMatchingNotification which expects
|
||||
// a DriverAppearedBlock to be passed as the 'reference' argument. The block
|
||||
// will be called for each object iterated over and if the block wants to keep
|
||||
// the object, it should call IOObjectRetain().
|
||||
typedef void (^DriverAppearedBlock)(io_object_t object);
|
||||
static void driverAppearedHandler(void *info, io_iterator_t iterator) {
|
||||
DriverAppearedBlock block = (__bridge DriverAppearedBlock)info;
|
||||
if (!block) return;
|
||||
io_object_t object = 0;
|
||||
while ((object = IOIteratorNext(iterator))) {
|
||||
block(object);
|
||||
IOObjectRelease(object);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the driver to appear, then attach to and open it.
|
||||
- (void)waitForDriver:(CFDictionaryRef CF_RELEASES_ARGUMENT)matchingDict {
|
||||
IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
|
||||
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop],
|
||||
IONotificationPortGetRunLoopSource(notificationPort), kCFRunLoopDefaultMode);
|
||||
|
||||
io_iterator_t iterator = 0;
|
||||
|
||||
DriverAppearedBlock block = ^(io_object_t object) {
|
||||
// This calls `initWithTask`, `attach` and `start` in `SantaDriverClient`
|
||||
kern_return_t kr = IOServiceOpen(object, mach_task_self(), 0, &self->_connection);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
LOGE(@"Failed to open santa-driver service: 0x%X", kr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Call `open` in `SantaDriverClient`
|
||||
kr = IOConnectCallMethod(self->_connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
if (kr == kIOReturnExclusiveAccess) {
|
||||
LOGE(@"A client is already connected");
|
||||
exit(2);
|
||||
} else if (kr != kIOReturnSuccess) {
|
||||
LOGE(@"An error occurred while opening the connection: 0x%X", kr);
|
||||
exit(3);
|
||||
}
|
||||
|
||||
// Release the iterator to disarm the notifications.
|
||||
IOObjectRelease(iterator);
|
||||
IONotificationPortDestroy(notificationPort);
|
||||
|
||||
self->_connectionEstablished = YES;
|
||||
};
|
||||
|
||||
LOGI(@"Waiting for Santa driver to become available");
|
||||
|
||||
IOServiceAddMatchingNotification(notificationPort, kIOMatchedNotification, matchingDict,
|
||||
driverAppearedHandler, (__bridge_retained void *)block,
|
||||
&iterator);
|
||||
|
||||
// Call the handler once to 'empty' the iterator, arming it. If the driver is already loaded
|
||||
// this will immediately cause the connection to be fully established.
|
||||
driverAppearedHandler((__bridge_retained void *)block, iterator);
|
||||
}
|
||||
|
||||
#pragma mark Incoming messages
|
||||
|
||||
- (void)listenForDecisionRequests:(void (^)(santa_message_t))callback {
|
||||
[self listenForRequestsOfType:QUEUETYPE_DECISION withCallback:callback];
|
||||
}
|
||||
|
||||
- (void)listenForLogRequests:(void (^)(santa_message_t))callback {
|
||||
[self listenForRequestsOfType:QUEUETYPE_LOG withCallback:callback];
|
||||
}
|
||||
|
||||
- (void)listenForRequestsOfType:(santa_queuetype_t)type
|
||||
withCallback:(void (^)(santa_message_t))callback {
|
||||
while (!self.connectionEstablished)
|
||||
usleep(100000); // 100ms
|
||||
|
||||
// Allocate a mach port to receive notifactions from the IODataQueue
|
||||
mach_port_t receivePort = IODataQueueAllocateNotificationPort();
|
||||
if (receivePort == MACH_PORT_NULL) {
|
||||
LOGD(@"Failed to allocate notification port");
|
||||
return;
|
||||
}
|
||||
|
||||
// This will call registerNotificationPort() inside our user client class
|
||||
kern_return_t kr = IOConnectSetNotificationPort(self.connection, type, receivePort, 0);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
LOGD(@"Failed to register notification port for type %d: 0x%X", type, kr);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will call clientMemoryForType() inside our user client class.
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
kr = IOConnectMapMemory(self.connection, type, mach_task_self(), &address, &size, kIOMapAnywhere);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
LOGD(@"Failed to map memory for type %d: 0x%X", type, kr);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
return;
|
||||
}
|
||||
|
||||
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
|
||||
|
||||
do {
|
||||
while (IODataQueueDataAvailable(queueMemory)) {
|
||||
santa_message_t vdata;
|
||||
uint32_t dataSize = sizeof(vdata);
|
||||
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
|
||||
if (kr == kIOReturnSuccess) {
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION: {
|
||||
dispatch_async(self.dmAuthQueue, ^{
|
||||
callback(vdata);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case QUEUETYPE_LOG: {
|
||||
dispatch_async(self.dmNotifiyQueue, ^{
|
||||
callback(vdata);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Error dequeuing data for type %d: 0x%X", type, kr);
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
|
||||
|
||||
IOConnectUnmapMemory(self.connection, type, mach_task_self(), address);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
}
|
||||
|
||||
#pragma mark Outgoing messages
|
||||
|
||||
- (kern_return_t)postAction:(santa_action_t)action forMessage:(santa_message_t)sm {
|
||||
santa_vnode_id_t vnodeId = sm.vnode_id;
|
||||
switch (action) {
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientAllowBinary, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
case ACTION_RESPOND_DENY:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientDenyBinary, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
case ACTION_RESPOND_ACK:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientAcknowledgeBinary, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientAllowCompiler, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
default: return KERN_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheCounts {
|
||||
uint32_t input_count = 2;
|
||||
uint64_t cache_counts[2] = {0, 0};
|
||||
|
||||
IOConnectCallScalarMethod(_connection, kSantaUserClientCacheCount, 0, 0, cache_counts,
|
||||
&input_count);
|
||||
|
||||
return @[ @(cache_counts[0]), @(cache_counts[1]) ];
|
||||
}
|
||||
|
||||
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly {
|
||||
const uint64_t nonRoot = nonRootOnly;
|
||||
|
||||
return IOConnectCallScalarMethod(_connection, kSantaUserClientClearCache, &nonRoot, 1, 0, 0) ==
|
||||
KERN_SUCCESS;
|
||||
}
|
||||
|
||||
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId {
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientRemoveCacheEntry, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
}
|
||||
|
||||
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
|
||||
uint64_t output;
|
||||
uint32_t outputCnt = 1;
|
||||
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientCheckCache, NULL, 0, &vnodeID,
|
||||
sizeof(santa_vnode_id_t), &output, &outputCnt, NULL, 0);
|
||||
return (santa_action_t)output;
|
||||
}
|
||||
|
||||
- (NSArray *)cacheBucketCount {
|
||||
santa_bucket_count_t counts = {};
|
||||
size_t size = sizeof(counts);
|
||||
|
||||
NSMutableArray *a = [NSMutableArray array];
|
||||
|
||||
do {
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientCacheBucketCount, &counts, size,
|
||||
&counts, &size);
|
||||
for (uint64_t i = 0; i < sizeof(counts.per_bucket) / sizeof(uint16_t); ++i) {
|
||||
[a addObject:@(counts.per_bucket[i])];
|
||||
}
|
||||
} while (counts.start > 0);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters {
|
||||
while (!self.connectionEstablished)
|
||||
usleep(100000); // 100ms
|
||||
|
||||
uint64_t n = 0;
|
||||
uint32_t n_len = 1;
|
||||
|
||||
for (NSString *filter in filters) {
|
||||
char buffer[MAXPATHLEN];
|
||||
if (![filter getFileSystemRepresentation:buffer maxLength:MAXPATHLEN]) {
|
||||
LOGE(@"Invalid filemod prefix filter: %@", filter);
|
||||
continue;
|
||||
}
|
||||
|
||||
kern_return_t ret =
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
|
||||
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
|
||||
if (ret != kIOReturnSuccess) {
|
||||
LOGE(@"Failed to add prefix filter: %s error: 0x%x", buffer, ret);
|
||||
|
||||
// If the tree is full, stop.
|
||||
// TODO(bur): Maybe continue here with some smarts around the size? Shorter, not yet iterated
|
||||
// prefixes (that may fit) are ignored. Seems worth it not to spam the driver.
|
||||
if (ret == kIOReturnNoResources) {
|
||||
LOGE(@"Prefix filter tree is full!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
LOGI(@"Added prefix filter: %s", buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fileModificationPrefixFilterReset {
|
||||
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,14 +14,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
#include "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
|
||||
const pid_t PID_MAX = 99999;
|
||||
|
||||
@interface SNTEndpointSecurityManager : NSObject <SNTEventProvider>
|
||||
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file;
|
||||
|
||||
@@ -29,6 +26,10 @@ const pid_t PID_MAX = 99999;
|
||||
- (void)setIsCompilerPID:(pid_t)pid;
|
||||
- (void)setNotCompilerPID:(pid_t)pid;
|
||||
|
||||
// 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;
|
||||
|
||||
@property(nonatomic, copy) void (^decisionCallback)(santa_message_t);
|
||||
@property(nonatomic, copy) void (^logCallback)(santa_message_t);
|
||||
@property(readonly, nonatomic) es_client_t *client;
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <libproc.h>
|
||||
#include <atomic>
|
||||
|
||||
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
|
||||
static const pid_t PID_MAX = 99999;
|
||||
|
||||
@interface SNTEndpointSecurityManager () {
|
||||
std::atomic<bool> _compilerPIDs[PID_MAX];
|
||||
}
|
||||
@@ -31,7 +34,6 @@
|
||||
@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
|
||||
|
||||
@@ -42,11 +44,10 @@
|
||||
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);
|
||||
@@ -55,7 +56,6 @@
|
||||
_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;
|
||||
@@ -69,6 +69,24 @@
|
||||
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];
|
||||
@@ -83,9 +101,7 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -111,9 +127,10 @@
|
||||
// Create a transitive rule if the file was modified by a running compiler
|
||||
if ([self isCompilerPID:pid]) {
|
||||
santa_message_t sm = {};
|
||||
BOOL truncated = [self 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);
|
||||
@@ -176,6 +193,19 @@
|
||||
});
|
||||
return;
|
||||
}
|
||||
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.
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
case ES_EVENT_TYPE_NOTIFY_FORK: {
|
||||
// Skip the standard pipeline and just log.
|
||||
if (![config enableForkAndExitLogging]) return;
|
||||
@@ -199,30 +229,42 @@
|
||||
|
||||
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, ^{
|
||||
@@ -277,6 +319,10 @@
|
||||
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: {
|
||||
@@ -303,8 +349,7 @@
|
||||
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;
|
||||
@@ -318,8 +363,7 @@
|
||||
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;
|
||||
@@ -329,8 +373,7 @@
|
||||
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;
|
||||
@@ -373,10 +416,9 @@
|
||||
targetProcess = m->process;
|
||||
NSString *p = @(m->event.link.target_dir->path.data);
|
||||
p = [p stringByAppendingPathComponent:@(m->event.link.target_filename.data)];
|
||||
[self populateBufferFromString:p.UTF8String
|
||||
length:p.length
|
||||
buffer:sm.newpath
|
||||
size:sizeof(sm.newpath)];
|
||||
[SNTEndpointSecurityManager populateBufferFromString:p.UTF8String
|
||||
buffer:sm.newpath
|
||||
size:sizeof(sm.newpath)];
|
||||
callback = self.logCallback;
|
||||
break;
|
||||
}
|
||||
@@ -395,7 +437,9 @@
|
||||
|
||||
// Deny auth exec events if the path doesn't fit in the santa message.
|
||||
// TODO(bur/rah): Add support for larger paths.
|
||||
if ([self populateBufferFromESFile:targetFile buffer:sm.path size:sizeof(sm.path)] &&
|
||||
if ([SNTEndpointSecurityManager populateBufferFromESFile:targetFile
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)] &&
|
||||
m->event_type == ES_EVENT_TYPE_AUTH_EXEC) {
|
||||
LOGE(@"path is truncated, deny: %s", sm.path);
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
@@ -417,6 +461,9 @@
|
||||
sm.ppid = targetProcess->original_ppid;
|
||||
proc_name((m->event_type == ES_EVENT_TYPE_AUTH_EXEC) ? sm.ppid : sm.pid, sm.pname, 1024);
|
||||
callback(sm);
|
||||
if (sm.args_array) {
|
||||
CFBridgingRelease(sm.args_array);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listenForDecisionRequests:(void (^)(santa_message_t))callback API_AVAILABLE(macos(10.15)) {
|
||||
@@ -433,6 +480,10 @@
|
||||
// This is in the decision callback because it's used for detecting
|
||||
// the exit of a 'compiler' used by transitive whitelisting.
|
||||
ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
|
||||
// This is in the decision callback because it's used for clearing the
|
||||
// caches when a disk is unmounted.
|
||||
ES_EVENT_TYPE_NOTIFY_UNMOUNT,
|
||||
};
|
||||
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
|
||||
if (sret != ES_RETURN_SUCCESS) LOGE(@"Unable to subscribe to auth events: %d", sret);
|
||||
@@ -440,7 +491,7 @@
|
||||
// There's a gap between creating a client and subscribing to events. Creating the client
|
||||
// triggers a cache flush automatically but any events that happen in this gap could be allowed
|
||||
// and cached, so we force the cache to flush again.
|
||||
[self flushCacheNonRootOnly:YES];
|
||||
[self flushCacheNonRootOnly:NO];
|
||||
}
|
||||
|
||||
- (void)listenForLogRequests:(void (^)(santa_message_t))callback API_AVAILABLE(macos(10.15)) {
|
||||
@@ -523,22 +574,18 @@
|
||||
#pragma mark helpers
|
||||
|
||||
// Returns YES if the path was truncated.
|
||||
// The populated path will be NUL terminated.
|
||||
- (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
|
||||
return [self populateBufferFromString:file->path.data
|
||||
length:file->path.length
|
||||
buffer:buffer
|
||||
size:size];
|
||||
// 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];
|
||||
}
|
||||
|
||||
// Returns YES if the path was truncated.
|
||||
// The populated path will be NUL terminated.
|
||||
- (BOOL)populateBufferFromString:(const char *)string
|
||||
length:(size_t)length
|
||||
buffer:(char *)buffer
|
||||
size:(size_t)size {
|
||||
if (length++ > size) length = size;
|
||||
return strlcpy(buffer, string, length) >= length;
|
||||
// The populated buffer will be NUL terminated.
|
||||
+ (BOOL)populateBufferFromString:(const char *)string buffer:(char *)buffer size:(size_t)size {
|
||||
return strlcpy(buffer, string, size) >= size;
|
||||
}
|
||||
|
||||
- (BOOL)populateRenamedNewPathFromESMessage:(es_event_rename_t)mv
|
||||
@@ -549,16 +596,15 @@
|
||||
case ES_DESTINATION_TYPE_NEW_PATH: {
|
||||
NSString *p = @(mv.destination.new_path.dir->path.data);
|
||||
p = [p stringByAppendingPathComponent:@(mv.destination.new_path.filename.data)];
|
||||
truncated = [self populateBufferFromString:p.UTF8String
|
||||
length:p.length
|
||||
buffer:buffer
|
||||
size:size];
|
||||
truncated = [SNTEndpointSecurityManager populateBufferFromString:p.UTF8String
|
||||
buffer:buffer
|
||||
size:size];
|
||||
break;
|
||||
}
|
||||
case ES_DESTINATION_TYPE_EXISTING_FILE: {
|
||||
truncated = [self populateBufferFromESFile:mv.destination.existing_file
|
||||
buffer:buffer
|
||||
size:size];
|
||||
truncated = [SNTEndpointSecurityManager populateBufferFromESFile:mv.destination.existing_file
|
||||
buffer:buffer
|
||||
size:size];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
@protocol SNTEventProvider <NSObject>
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string>Google, LLC.</string>
|
||||
<key>NSSystemExtensionUsageDescription</key>
|
||||
<string>Santa knows who is naughty and nice.</string>
|
||||
<key>NSEndpointSecurityEarlyBoot</key>
|
||||
<true/>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>com.google.santa.daemon</string>
|
||||
</dict>
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
@class SNTCachedDecision;
|
||||
@class SNTStoredEvent;
|
||||
@class SNTAllowlistInfo;
|
||||
|
||||
///
|
||||
/// Abstract interface for logging execution and file write events to syslog
|
||||
@@ -37,7 +38,8 @@
|
||||
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events;
|
||||
- (void)logFork:(santa_message_t)message;
|
||||
- (void)logExit:(santa_message_t)message;
|
||||
- (void)writeLog:(NSString *)log;
|
||||
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo;
|
||||
- (void)forceFlush;
|
||||
|
||||
// Methods for storing, retrieving, and removing cached decisions.
|
||||
- (void)cacheDecision:(SNTCachedDecision *)cd;
|
||||
@@ -54,7 +56,7 @@
|
||||
- (NSString *)nameForGID:(gid_t)gid;
|
||||
- (NSString *)sanitizeString:(NSString *)inStr;
|
||||
- (NSString *)serialForDevice:(NSString *)devPath;
|
||||
- (NSString *)originalPathForTranslocation:(santa_message_t)message;
|
||||
- (NSString *)originalPathForTranslocation:(const santa_message_t *)message;
|
||||
|
||||
// A cache for usernames and groups.
|
||||
@property(readonly, nonatomic) NSCache<NSNumber *, NSString *> *userNameMap;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import "Source/santad/SNTDatabaseController.h"
|
||||
|
||||
#import "Source/santad/Logs/SNTFileEventLog.h"
|
||||
#import "Source/santad/Logs/SNTProtobufEventLog.h"
|
||||
#import "Source/santad/Logs/SNTSyslogEventLog.h"
|
||||
|
||||
@interface SNTEventLog ()
|
||||
@@ -92,7 +93,11 @@
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)writeLog:(NSString *)log {
|
||||
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)forceFlush {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
@@ -380,7 +385,11 @@
|
||||
Security.framework so that we can still build against the 10.11 SDK. If the path has not been
|
||||
translocated or if running on macOS prior to 10.12, this method returns nil.
|
||||
*/
|
||||
- (NSString *)originalPathForTranslocation:(santa_message_t)message {
|
||||
- (NSString *)originalPathForTranslocation:(const santa_message_t *)message {
|
||||
if (!message) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// The first time this function is called, we attempt to find the addresses of
|
||||
// SecTranslocateIsTranslocatedURL and SecTranslocateCreateOriginalPathForURL inside of the
|
||||
// Security.framework library. If we were successful, handle will be non-NULL and is never
|
||||
@@ -405,7 +414,7 @@
|
||||
if (!IsTranslocatedURL || !CreateOriginalPathForURL) return nil;
|
||||
|
||||
// Determine if the executable URL has been translocated or not.
|
||||
CFURLRef cfExecURL = (__bridge CFURLRef)[NSURL fileURLWithPath:@(message.path)];
|
||||
CFURLRef cfExecURL = (__bridge CFURLRef)[NSURL fileURLWithPath:@(message->path)];
|
||||
bool isTranslocated = false;
|
||||
if (!IsTranslocatedURL(cfExecURL, &isTranslocated, NULL) || !isTranslocated) return nil;
|
||||
|
||||
@@ -413,7 +422,7 @@
|
||||
// launched the executable. So we temporarily drop from root down to this uid, then reset.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
pthread_setugid_np(message.uid, message.gid);
|
||||
pthread_setugid_np(message->uid, message->gid);
|
||||
NSURL *origURL = CFBridgingRelease(CreateOriginalPathForURL(cfExecURL, NULL));
|
||||
pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE);
|
||||
#pragma clang diagnostic pop
|
||||
@@ -422,7 +431,7 @@
|
||||
}
|
||||
|
||||
+ (instancetype)logger {
|
||||
static SNTEventLog *logger;
|
||||
static SNTEventLog *logger = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
switch ([[SNTConfigurator configurator] eventLogType]) {
|
||||
@@ -434,7 +443,15 @@
|
||||
logger = [[SNTFileEventLog alloc] init];
|
||||
break;
|
||||
}
|
||||
default: logger = nil;
|
||||
case SNTEventLogTypeProtobuf: {
|
||||
logger = [[SNTProtobufEventLog alloc] init];
|
||||
break;
|
||||
}
|
||||
case SNTEventLogTypeNull: {
|
||||
// Messages sent to nil objects do nothing, which is perfect for a null logger.
|
||||
logger = nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return logger;
|
||||
|
||||
@@ -96,4 +96,10 @@
|
||||
[self.buffer setLength:0];
|
||||
}
|
||||
|
||||
- (void)forceFlush {
|
||||
dispatch_sync(self.q, ^{
|
||||
[self flushBuffer];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user