Compare commits

...

67 Commits

Author SHA1 Message Date
Russell Hancox
e639574973 Project: Fix layering for tests (#813) 2022-05-12 16:52:11 -04:00
Russell Hancox
636f9ea873 Project: Layering, missed a dependency (#812) 2022-05-12 14:49:18 -04:00
Russell Hancox
9099409915 Project: Enable layering check, fix all dependency violations (#811) 2022-05-12 14:26:08 -04:00
Russell Hancox
976f483a99 syncservice: Fix SNTSyncTest (#810)
Failing preflight early if the daemonConn doesn't return a response the tests. This fix is a bit awkward, I tried to add the defaults in setUp but then you can't overwrite the stubs in methods that need to do it
2022-05-12 09:54:00 -04:00
Tom Burgin
8a32b7a56b preflight sync: fix dispatch_group_wait return polarity (#809)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-11 14:55:42 -04:00
Tom Burgin
7eeb06b406 preflight sync: stop the sync if we cannot communicate with the daemon (#808)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-11 18:45:58 +00:00
Tom Burgin
4540a1c656 SNTConfigurator: remove mutability from sync state dict (#807)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-11 14:35:43 -04:00
Russell Hancox
acc7b32b24 GUI: Switch to UserNotification.framework notifications (#806) 2022-05-11 12:32:08 -04:00
Russell Hancox
b92d513f5d GUI: Fix message queuing (#805) 2022-05-11 09:59:38 -04:00
Tom Burgin
3458fccd4e santasyncservice: handle loading and unloading of the service in the pkg (#804)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-10 14:59:34 -04:00
Russell Hancox
fdfb00368c GUI: Update keys for EventDetailURL. (#802)
The previous change here (#797) was not backward compatible and would be difficult to roll out. This change restores the previously used key and adds 2 new ones for migration. The previous key is marked deprecated and will be removed in the future.
2022-05-09 13:46:13 -04:00
Tom Burgin
6bd369cfb2 santad: remove sema from sync service queue (#803)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-05-09 13:32:28 -04:00
Pete Markowsky
0df26c6214 Fix ES Mock Client Subscription issues (#801)
Fixes an issue with the ES mock where it was deleting all clients on an unsubscribe.
2022-05-06 14:34:42 -04:00
Russell Hancox
6e22da1d97 santad: Add 'null' event logger. Fixes #754 (#799) 2022-05-06 12:22:04 -04:00
Russell Hancox
1725809335 Add config to allow uploading all events (#800)
* Add config to allow uploading all events

This config can be enabled locally or by a sync server and causes the
client to upload all events, not just those for binaries that are or
would be blocked.

Fixes #689
2022-05-06 11:45:53 -04:00
Pete Markowsky
3eff49feda Added macos-12 to the build matrix. (#798) 2022-05-03 21:14:15 -04:00
Pete Markowsky
5caedebb06 Created a profiles package so provisioning profiles only need to be in one place. (#794) 2022-05-03 17:14:02 -04:00
Russell Hancox
d823028b72 Sync: Add option to enable event upload despite clean sync. (#796)
Related to #789
2022-05-03 15:15:42 -04:00
Russell Hancox
49b2d6e22a GUI: Add %bundle_or_file_sha% translation key (#797)
* GUI: Add %bundle_or_file_sha% translation key

This mimics the current behavior that %file_sha% previously had and
moves %file_sha% to the expected behavior or just showing the file's
SHA.

Related to #795
2022-05-03 14:59:01 -04:00
Russell Hancox
4236d57e96 Project: Update packaging script to do tarball creation in a scratch dir (#793) 2022-04-28 15:25:48 -04:00
Russell Hancox
36d463a1dc Project: Include syncservice.plist in release builds and loads (#792) 2022-04-28 14:42:19 -04:00
Tom Burgin
adbafd6bab syncservice: sign and package (#790)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-04-28 13:31:20 -04:00
Tom Burgin
b5ebe1259c syncservice: implementation and migration (#775)
* review updates

* fix test

* review updates

* log level cleanup

Co-authored-by: Tom Burgin <bur@chromium.org>
Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2022-04-27 14:54:56 -04:00
Ryan Diers
e0ae0f481b santa/gui: Update buttons to push style to better stand out (#788) 2022-04-19 20:08:35 -04:00
Matt W
8037c79fc0 Populate critical paths from the ES default mute set (#786)
* Populate critical paths from the ES default mute set

* Attempt to fix build on older macos

* Link ES to build SNTRuleTableTest

* Workflow test

* Use preprocessor macros to support building on older SDKs

* Add API availability
2022-04-18 15:11:43 -04:00
Walter Lee
892d303de1 Disable layering check for Objective-C, part two (#787) 2022-04-18 12:15:08 -04:00
Russell Hancox
ff3979263e santad: Use TTY path provided by ES (#785) 2022-04-15 12:48:06 -04:00
np5
01afefd3d4 santactl/sync: Fix event team ID decision value (#784) 2022-04-15 10:27:48 -04:00
Kent Ma
830627e7bc Docs: Add "Team ID" to description on AllowedPathRegex (#782) 2022-04-14 13:13:51 -04:00
Walter Lee
601d726fcc Disable layering check for Objective-C (#781) 2022-04-12 09:06:55 -04:00
Tom Burgin
0be1ca0199 ES_EVENT_TYPE_NOTIFY_UNMOUNT: flush the cache off the ES handler thread (#778)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-04-06 12:07:08 -04:00
Kent Ma
8602593149 Fix dead link (#774) 2022-03-25 13:33:08 -04:00
Matt W
9bca601ce6 Modified build target names for santa proto (#772) 2022-03-25 13:07:57 -04:00
Kent Ma
c73acd59d4 Update logo image of Santa (#773) 2022-03-25 12:46:34 -04:00
Russell Hancox
3c334e8882 Project: Fix coverage collection (#770) 2022-03-24 11:33:46 -04:00
Russell Hancox
5f811cadf8 Project: Update apple_rules dep, add .bazelversion for bazelisk users (#769) 2022-03-23 17:34:04 -04:00
Russell Hancox
4252475de0 Project: Fix fallback version (#767) 2022-03-23 15:13:30 -04:00
Kent Ma
45f1822681 Exclude bazel-out from test coverage generation (#768) 2022-03-23 15:10:46 -04:00
Russell Hancox
498a23d907 Project: Make versioning dynamic through bazel's --embed-label. (#766)
The apple_rules allow versioning using an apple_bundle_version rule that extracts elements from an embedded label. We haven't been able to use this until now because the kernel extension needed access to the version in a define.
2022-03-23 14:53:51 -04:00
Russell Hancox
5dff8a18f4 santad: Split ES cache into root/non-root varieties (#765) 2022-03-23 09:43:14 -04:00
Russell Hancox
676c02626d santactl/metrics: Allow filtering metrics (#763) 2022-03-22 18:12:14 +00:00
Russell Hancox
64950d0a99 Project: Show test errors in output from CI (#764) 2022-03-22 11:39:01 -04:00
Kent Ma
16f74cb85c Remove references to santa-driver and the kernel extension from parts of the docs (#762) 2022-03-21 11:33:45 -04:00
Russell Hancox
aadc961429 santad: Clear caches when disks are unmounted. (#760)
This restores behavior that was recently removed
2022-03-18 13:38:35 -04:00
Russell Hancox
be66fd92f4 santactl/status: Re-org output in status re: USB Blocking. (#759) 2022-03-18 09:57:34 -04:00
Russell Hancox
feea349f25 Project: Remove kext signing/packaging (#755) 2022-03-16 17:08:59 -04:00
Kent Ma
1c04c3a257 Remove code guarded by #ifdef kernel macros (#752)
* Remove code guarded by #ifdef kernel macros
2022-03-15 14:38:40 -04:00
np5
818d3f645f santactl/sync: Add model identifier to preflight request (#751) 2022-03-15 14:24:05 +00:00
Pete Markowsky
15d6bb1f14 Made santad an early boot client to prevent racing other pids. (#750)
Make santad an early boot Endpoint Security Framework Client.
2022-03-15 10:16:40 -04:00
Kent Ma
211dbd123f Remove the Santa kernel extension. (#749)
This includes:

* All of the code in Source/santa_driver containing the kernel extension
* The SNTDriverManager event provider
* All workflows in our CI related to testing if the driver builds
* Installation of the driver in install.sh. Note that code uninstalling existing instances of the driver is still intentionally kept present.
* Kernel extension-specific build rules
* Renames SNTKernelCommon to SNTCommon
* Driver version output from santactl version
* The [SNTConfigurator EnableSystemExtension] configuration key
2022-03-14 18:17:02 -04:00
Matt W
c67364fe76 Protobuf support, maildir format logging (#731)
* Initial protobuf support, maildir logging

Fix build issues in the integration test

Deduped some test code

Formatting

Address feedback from draft PR

Removed legacy labels. Updated docs.

Add in metrics. Fix protobuf logging test.

* Now use the Any proto for the LogBatch wrapper

* Changes based on PR feedback

* Added gauge metrics for spool dir

* Formatting

* Add event time to proto

* Fix build issue after rebase

* Update BUILD rules

* Updated language around protobuf logging to mark as beta
2022-03-14 15:46:52 -04:00
Pete Markowsky
2043983f69 Fix typo in SNTDeviceManager tests & ensure they run in the CI. (#746) 2022-03-14 12:57:07 -04:00
Russell Hancox
2f408936a0 Project: Disable bazel layering_check feature for most rules (#742) 2022-03-10 10:07:15 -05:00
Russell Hancox
02c1d0f267 Project: Bump version to 2022.3 (#745) 2022-03-10 09:35:44 -05:00
Pete Markowsky
4728c346cc Fix uninstall.sh to remove the metric & bundle services. (#743) 2022-03-09 18:00:45 -05:00
Pete Markowsky
9588dd8a0e Fix: Issue with SNTMetricHTTPWriter Timeouts (#741)
Fix issue with santametricservice timing out due to incorrect timeout argument.
2022-03-08 14:12:57 -05:00
Russell Hancox
e3e48aed1b Packaging: Keep package versions simple (#737) 2022-03-02 10:36:39 -05:00
Russell Hancox
e60f9cf6c5 Project: Add build version (#736) 2022-02-28 14:18:18 -05:00
Kent Ma
c7e309ccb1 Add a USB device blocking popup. (#728)
* Add a USB device blocking popup.
* Refactor SNTNotificationManager and SNTMessageWindowController to make
  for generalized notification logic
* Add the configuration keys for custom block messages and resize window
2022-02-28 13:30:56 -05:00
Russell Hancox
ad8aafbd07 Project: Bump version to 2022.2 (#734) 2022-02-17 11:36:21 -05:00
Russell Hancox
9e671c3dee Project: Add arm64 to hostArchitectures for productbuild (#733)
This should avoid prompts to install Rosetta when installing the .pkg
2022-02-16 20:02:12 -05:00
Russell Hancox
d97abe36f2 santad: Fix fail open tests in SNTExecutionControllerTest (#730) 2022-02-14 20:13:08 -05:00
Pete Markowsky
faa8946056 Fix: remediate a crash in santametricservice (#729)
* Fix issue with task cancelation.

* Make export timeouts configurable.

This allows an export timeout to be set via configuration and eases testing.
2022-02-14 13:51:29 -05:00
Kent Ma
8b2b1f0bfc Report USB blocking status with santactl status (#727)
* Report USB blocking status with santactl status
2022-02-10 16:02:40 -05:00
Kent Ma
16678cd5a0 Update version of bazel rules_apple to fix broken 12.3 builds (#726) 2022-02-10 14:32:43 -05:00
Matt W
0bd6a199a3 Fix additional strlcpy issue, simplify call paths (#723)
* Fix additional strlcpy issue, simplify call paths

* Remove unused interface from header
2022-02-03 12:53:22 -05:00
Russell Hancox
58e2b7e1b8 santad: Add fail-closed mode (#722) 2022-01-28 18:29:18 -05:00
151 changed files with 5079 additions and 5218 deletions

1
.bazelversion Normal file
View File

@@ -0,0 +1 @@
5.0.0

View File

@@ -12,7 +12,6 @@ jobs:
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
@@ -26,16 +25,11 @@ jobs:
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
@@ -46,13 +40,6 @@ jobs:
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]
@@ -66,7 +53,7 @@ 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'
@@ -75,31 +62,18 @@ jobs:
- 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
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=ci --test_output=errors
test_coverage:
runs-on: macos-11
@@ -113,7 +87,7 @@ 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:

73
BUILD
View File

@@ -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
@@ -49,7 +54,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 +65,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 +74,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 +100,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 +110,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 +152,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 +186,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 = [

View File

@@ -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') &lt; 0">kext.pkg</pkg-ref>
</installer-gui-script>

View File

@@ -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}"

View File

@@ -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

View File

@@ -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

View File

@@ -15,8 +15,8 @@
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<false/>
<key>KeepAlive</key>
<true/>
<false/>
</dict>
</plist>

View File

@@ -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

View File

@@ -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

View File

@@ -1,15 +1,14 @@
# Santa [![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/google/santa/badge.svg?branch=main)](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.

View File

@@ -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"],
@@ -85,28 +122,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,23 +145,6 @@ 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"],
@@ -192,9 +198,12 @@ objc_library(
srcs = ["SNTXPCControlInterface.m"],
hdrs = ["SNTXPCControlInterface.h"],
deps = [
":SNTCommonEnums",
":SNTConfigurator",
":SNTRule",
":SNTStoredEvent",
":SNTXPCUnprivilegedControlInterface",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
@@ -216,21 +225,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 +241,8 @@ objc_library(
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
deps = [
":SNTCommon",
":SNTCommonEnums",
":SNTKernelCommon",
":SNTRule",
":SNTStoredEvent",
":SNTXPCBundleServiceInterface",

View 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

View 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

View File

@@ -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;

View File

@@ -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%"

View File

@@ -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;

View File

@@ -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

View File

@@ -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";

View File

@@ -26,7 +26,7 @@
#pragma mark - Daemon Settings
///
/// The operating mode.
/// The operating mode. Defaults to MONITOR.
///
@property(readonly, nonatomic) SNTClientMode clientMode;
@@ -35,6 +35,17 @@
///
- (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;
///
/// The regex of allowed paths. Regexes are specified in ICU format.
///
@@ -140,8 +151,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 +173,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 +216,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 +277,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 +412,17 @@
///
@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, forks and exits will be logged. Defaults to false.
///
@@ -427,6 +494,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.
///

View File

@@ -28,7 +28,7 @@
@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?
@@ -46,6 +46,7 @@ static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The keys managed by a mobileconfig.
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 +67,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 +81,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 +103,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 +112,13 @@ 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";
// 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 +148,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,6 +172,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kEventDetailTextKey : string,
kUnknownBlockMessage : string,
kBannedBlockMessage : string,
kBannedUSBBlockMessage : string,
kRemountUSBBlockMessage : string,
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kSyncBaseURLKey : string,
@@ -178,8 +192,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,7 +208,9 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kMetricFormat : string,
kMetricURL : string,
kMetricExportInterval : number,
kMetricExportTimeout : number,
kMetricExtraLabels : dictionary,
kEnableAllEventUploadKey : number,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -355,6 +374,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 +398,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
return [self configStateSet];
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
@@ -429,6 +464,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];
@@ -553,6 +594,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 +696,72 @@ 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)enableForkAndExitLogging {
NSNumber *number = self.configState[kEnableForkAndExitLogging];
return number ? [number boolValue] : NO;
@@ -751,6 +844,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];
}

View 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

View 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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}
/**

View File

@@ -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"
}
];

View File

@@ -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
}

View File

@@ -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 */

View File

@@ -49,4 +49,9 @@
///
+ (NSString *)longHostname;
///
/// @return Model Identifier
///
+ (NSString *)modelIdentifier;
@end

View File

@@ -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 {

View File

@@ -51,11 +51,11 @@
- (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;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
@end

View File

@@ -27,13 +27,10 @@ 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;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
// "teamid.com.google.santa.daemon.xpc"
NSString *t = cs.signingInformation[@"teamid"];
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
}
+ (NSString *)systemExtensionID {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

145
Source/common/santa.proto Normal file
View File

@@ -0,0 +1,145 @@
//
// !!! 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;
}
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;
}

View File

@@ -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/**"]),
@@ -61,10 +76,10 @@ macos_application(
],
entitlements = "Santa.app.entitlements",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
"//conditions:default": "//profiles:santa_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],

View File

@@ -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>

View 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>

View File

@@ -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"/>

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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"
///

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
""",
)

View File

@@ -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>

View File

@@ -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, &microsecs);
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, &microsecs);
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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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; }

View File

@@ -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({
"//:ci_build": None,
"//conditions:default": "//profiles:santa_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santabs_lib"],

View File

@@ -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",
"//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,13 +101,23 @@ santa_unit_test(
santa_unit_test(
name = "SNTCommandMetricsTest",
srcs = ["Commands/SNTCommandMetricsTest.m"],
srcs = [
"SNTCommand.h",
"SNTCommandController.h",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetricsTest.m",
],
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",
"@OCMock",
"@MOLXPCConnection",
],
)

View File

@@ -19,4 +19,5 @@
@interface SNTCommandMetrics : SNTCommand <SNTCommandProtocol>
- (void)prettyPrintMetrics:(NSDictionary *)metircs asJSON:(BOOL)exportJSON;
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args;
@end

View File

@@ -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);
}

View File

@@ -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

View File

@@ -80,9 +80,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);
@@ -188,6 +188,10 @@ REGISTER_COMMAND_NAME(@"status")
@"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),
@@ -224,11 +228,18 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
printf(" %-25s | %s\n", "USB Remounting Mode:",
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
}
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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:SNTStoredEvent",
"//Source/common:SNTLogging",
],
)
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",
deps = [
":file_event_logs",
":protobuf_event_logs",
":syslog_event_logs",
"//Source/common:SNTCommon",
],
hdrs = ["Logs/SNTEventLog.h"],
)
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 = [
":database_controller",
":endpoint_security_manager",
":event_logs",
":SNTApplicationCoreMetrics",
"//Source/common:SantaCache",
"//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:SantaCache",
"//Source/santad:SNTApplicationCoreMetrics",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
@@ -97,6 +248,9 @@ objc_library(
"EventProviders/EndpointSecurityTestUtil.h",
"EventProviders/EndpointSecurityTestUtil.mm",
],
hdrs = [
"EventProviders/EndpointSecurityTestUtil.h",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
@@ -135,10 +289,10 @@ macos_bundle(
],
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",
"//conditions:default": "//profiles:daemon_dev",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
@@ -148,6 +302,11 @@ macos_bundle(
santa_unit_test(
name = "SNTExecutionControllerTest",
srcs = [
"SNTExecutionController.h",
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTEventTable.h",
"DataLayer/SNTRuleTable.h",
"EventProviders/SNTEventProvider.h",
"SNTExecutionControllerTest.m",
],
sdk_dylibs = [
@@ -158,17 +317,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",
@@ -205,6 +366,7 @@ santa_unit_test(
],
deps = [
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
@@ -213,6 +375,9 @@ santa_unit_test(
"@MOLCertificate",
"@MOLCodesignChecker",
],
sdk_dylibs = [
"EndpointSecurity",
],
)
santa_unit_test(
@@ -230,7 +395,9 @@ santa_unit_test(
],
deps = [
":EndpointSecurityTestLib",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
@@ -239,6 +406,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 +419,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 +431,9 @@ santa_unit_test(
santa_unit_test(
name = "SNTApplicationTest",
srcs = [
"SNTApplication.h",
"SNTApplicationTest.m",
"SNTDatabaseController.h",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
@@ -273,6 +446,9 @@ santa_unit_test(
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTConfigurator",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
@@ -310,6 +486,7 @@ santa_unit_test(
deps = [
":SNTApplicationCoreMetrics",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
@@ -317,14 +494,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"],

View File

@@ -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 {

View File

@@ -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,17 @@ 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);
#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,

View File

@@ -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,31 @@ es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
return ES_NEW_CLIENT_RESULT_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;
};

View File

@@ -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

View File

@@ -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

View File

@@ -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;
@@ -216,9 +219,14 @@ long mountArgsToMask(NSArray<NSString *> *args) {
return;
}
SNTDeviceEvent *event = [[SNTDeviceEvent alloc]
initWithOnName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntonname]
fromName:[NSString stringWithUTF8String:m->event.mount.statfs->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) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
@@ -231,6 +239,11 @@ long mountArgsToMask(NSArray<NSString *> *args) {
newMode);
[self remount:disk mountMode:newMode];
}
if (self.deviceBlockCallback) {
self.deviceBlockCallback(event);
}
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
}
@@ -298,3 +311,5 @@ long mountArgsToMask(NSArray<NSString *> *args) {
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -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"
@@ -132,10 +133,28 @@
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 triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
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 +167,27 @@
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = YES;
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 triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
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");
}
@end

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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];
}
@@ -42,10 +45,8 @@
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];
_prefixTree = new SNTPrefixTree();
_esAuthQueue =
@@ -111,9 +112,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 +178,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;
@@ -277,6 +292,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: {
@@ -373,10 +392,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 +413,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 +437,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 +456,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 +467,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 +550,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 +572,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;
}
}

View File

@@ -14,7 +14,7 @@
#import <Foundation/Foundation.h>
#include "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTCommon.h"
@protocol SNTEventProvider <NSObject>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -96,4 +96,10 @@
[self.buffer setLength:0];
}
- (void)forceFlush {
dispatch_sync(self.q, ^{
[self flushBuffer];
});
}
@end

View File

@@ -1,4 +1,4 @@
/// Copyright 2016 Google Inc. All rights reserved.
/// 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.
@@ -12,21 +12,17 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCSyncdInterface.h"
#import <Foundation/Foundation.h>
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/Santa.pbobjc.h"
@implementation SNTXPCSyncdInterface
NS_ASSUME_NONNULL_BEGIN
+ (NSXPCInterface *)syncdInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
@protocol SNTLogOutput<NSObject>
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
argumentIndex:0
ofReply:NO];
return r;
}
- (void)logEvent:(SNTPBSantaMessage *)event;
- (void)flush;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,21 @@
/// 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/santad/Logs/SNTEventLog.h"
@interface SNTProtobufEventLog : SNTEventLog
- (void)logFileModification:(santa_message_t)message;
@end

View File

@@ -0,0 +1,348 @@
/// 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/santad/Logs/SNTProtobufEventLog.h"
#import <EndpointSecurity/EndpointSecurity.h>
#import <Foundation/Foundation.h>
#import <libproc.h>
#import <os/log.h>
#import "Source/common/SNTAllowlistInfo.h"
#import "Source/common/SNTCachedDecision.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/Santa.pbobjc.h"
#import "Source/santad/Logs/SNTSimpleMaildir.h"
@interface SNTProtobufEventLog ()
@property(readonly) id<SNTLogOutput> logOutput;
@end
@implementation SNTProtobufEventLog
- (instancetype)init {
SNTSimpleMaildir *mailDir = [[SNTSimpleMaildir alloc]
initWithBaseDirectory:[[SNTConfigurator configurator] mailDirectory]
filenamePrefix:@"out.log"
fileSizeThreshold:[[SNTConfigurator configurator] mailDirectoryFileSizeThresholdKB] * 1024
directorySizeThreshold:[[SNTConfigurator configurator] mailDirectorySizeThresholdMB] * 1024 *
1024
maxTimeBetweenFlushes:[[SNTConfigurator configurator] mailDirectoryEventMaxFlushTimeSec]];
return [self initWithLog:mailDir];
}
- (instancetype)initWithLog:(id<SNTLogOutput>)log {
if (!log) {
return nil;
}
self = [super init];
if (self) {
_logOutput = log;
}
return self;
}
- (void)forceFlush {
[self.logOutput flush];
}
- (void)logWithSantaMessage:(santa_message_t *)santaMsg
wrapper:(void (^)(SNTPBSantaMessage *))setMessage {
SNTPBSantaMessage *smpb = [[SNTPBSantaMessage alloc] init];
if (santaMsg && santaMsg->es_message) {
struct timespec ts = ((es_message_t *)santaMsg->es_message)->time;
NSDate *esTime =
[NSDate dateWithTimeIntervalSince1970:(double)(ts.tv_sec) + (((double)ts.tv_nsec) / 1E9)];
smpb.eventTime = [[GPBTimestamp alloc] initWithDate:esTime];
} else {
smpb.eventTime = [[GPBTimestamp alloc] initWithDate:[NSDate date]];
}
setMessage(smpb);
[self.logOutput logEvent:smpb];
}
- (void)logWithWrapper:(void (^)(SNTPBSantaMessage *))setMessage {
return [self logWithSantaMessage:nil wrapper:setMessage];
}
- (SNTPBFileModification_Action)protobufActionForSantaMessageAction:(santa_action_t)action {
switch (action) {
case ACTION_NOTIFY_DELETE: return SNTPBFileModification_Action_ActionDelete;
case ACTION_NOTIFY_EXCHANGE: return SNTPBFileModification_Action_ActionExchange;
case ACTION_NOTIFY_LINK: return SNTPBFileModification_Action_ActionLink;
case ACTION_NOTIFY_RENAME: return SNTPBFileModification_Action_ActionRename;
case ACTION_NOTIFY_WRITE: return SNTPBFileModification_Action_ActionWrite;
default: return SNTPBFileModification_Action_ActionUnknown;
}
}
- (NSString *)newpathForSantaMessage:(santa_message_t *)message {
if (!message) {
return nil;
}
switch (message->action) {
case ACTION_NOTIFY_EXCHANGE: OS_FALLTHROUGH;
case ACTION_NOTIFY_LINK: OS_FALLTHROUGH;
case ACTION_NOTIFY_RENAME: return @(message->newpath);
default: return nil;
}
}
- (NSString *)processPathForSantaMessage:(santa_message_t *)message {
if (!message) {
return nil;
}
// If we have an ES message, use the path provided by the ES framework.
// Otherwise, attempt to lookup the path. Note that this will fail if the
// process being queried has already exited.
if (message->es_message) {
switch (message->action) {
case ACTION_NOTIFY_DELETE: OS_FALLTHROUGH;
case ACTION_NOTIFY_EXCHANGE: OS_FALLTHROUGH;
case ACTION_NOTIFY_LINK: OS_FALLTHROUGH;
case ACTION_NOTIFY_RENAME: OS_FALLTHROUGH;
case ACTION_NOTIFY_WRITE: {
return @(((es_message_t *)message->es_message)->process->executable->path.data);
}
default: return nil;
}
} else {
char path[PATH_MAX];
path[0] = '\0';
proc_pidpath(message->pid, path, sizeof(path));
return @(path);
}
}
- (SNTPBProcessInfo *)protobufProcessInfoForSantaMessage:(santa_message_t *)message {
if (!message) {
return nil;
}
SNTPBProcessInfo *procInfo = [[SNTPBProcessInfo alloc] init];
procInfo.pid = message->pid;
procInfo.pidversion = message->pidversion;
procInfo.ppid = message->ppid;
procInfo.uid = message->uid;
procInfo.gid = message->gid;
procInfo.user = [self nameForUID:message->uid];
procInfo.group = [self nameForGID:message->gid];
return procInfo;
}
- (SNTPBExecution_Decision)protobufDecisionForCachedDecision:(SNTCachedDecision *)cd {
if (cd.decision & SNTEventStateBlock) {
return SNTPBExecution_Decision_DecisionDeny;
} else if (cd.decision & SNTEventStateAllow) {
return SNTPBExecution_Decision_DecisionAllow;
} else {
return SNTPBExecution_Decision_DecisionUnknown;
}
}
- (SNTPBExecution_Reason)protobufReasonForCachedDecision:(SNTCachedDecision *)cd {
switch (cd.decision) {
case SNTEventStateAllowBinary: return SNTPBExecution_Reason_ReasonBinary;
case SNTEventStateAllowCompiler: return SNTPBExecution_Reason_ReasonCompiler;
case SNTEventStateAllowTransitive: return SNTPBExecution_Reason_ReasonTransitive;
case SNTEventStateAllowPendingTransitive: return SNTPBExecution_Reason_ReasonPendingTransitive;
case SNTEventStateAllowCertificate: return SNTPBExecution_Reason_ReasonCert;
case SNTEventStateAllowScope: return SNTPBExecution_Reason_ReasonScope;
case SNTEventStateAllowTeamID: return SNTPBExecution_Reason_ReasonTeamId;
case SNTEventStateAllowUnknown: return SNTPBExecution_Reason_ReasonUnknown;
case SNTEventStateBlockBinary: return SNTPBExecution_Reason_ReasonBinary;
case SNTEventStateBlockCertificate: return SNTPBExecution_Reason_ReasonCert;
case SNTEventStateBlockScope: return SNTPBExecution_Reason_ReasonScope;
case SNTEventStateBlockTeamID: return SNTPBExecution_Reason_ReasonTeamId;
case SNTEventStateBlockUnknown: return SNTPBExecution_Reason_ReasonUnknown;
case SNTEventStateAllow: OS_FALLTHROUGH;
case SNTEventStateUnknown: OS_FALLTHROUGH;
case SNTEventStateBundleBinary: OS_FALLTHROUGH;
case SNTEventStateBlock: return SNTPBExecution_Reason_ReasonNotRunning;
}
return SNTPBExecution_Reason_ReasonUnknown;
}
- (SNTPBExecution_Mode)protobufModeForClientMode:(SNTClientMode)mode {
switch (mode) {
case SNTClientModeMonitor: return SNTPBExecution_Mode_ModeMonitor;
case SNTClientModeLockdown: return SNTPBExecution_Mode_ModeLockdown;
case SNTClientModeUnknown: return SNTPBExecution_Mode_ModeUnknown;
}
return SNTPBExecution_Mode_ModeUnknown;
}
- (void)logFileModification:(santa_message_t)message {
SNTPBFileModification *fileMod = [[SNTPBFileModification alloc] init];
fileMod.action = [self protobufActionForSantaMessageAction:message.action];
fileMod.path = @(message.path);
fileMod.newpath = [self newpathForSantaMessage:&message];
fileMod.process = @(message.pname);
fileMod.processPath = [self processPathForSantaMessage:&message];
fileMod.processInfo = [self protobufProcessInfoForSantaMessage:&message];
fileMod.machineId =
[[SNTConfigurator configurator] enableMachineIDDecoration] ? self.machineID : nil;
[self logWithSantaMessage:&message
wrapper:^(SNTPBSantaMessage *sm) {
sm.fileModification = fileMod;
}];
}
- (void)logExecution:(santa_message_t)message withDecision:(SNTCachedDecision *)cd {
SNTPBExecution *exec = [[SNTPBExecution alloc] init];
exec.decision = [self protobufDecisionForCachedDecision:cd];
exec.reason = [self protobufReasonForCachedDecision:cd];
exec.explain = cd.decisionExtra;
exec.sha256 = cd.sha256;
exec.certSha256 = cd.certSHA256;
exec.certCn = cd.certCommonName;
exec.quarantineURL = cd.quarantineURL;
exec.processInfo = [self protobufProcessInfoForSantaMessage:&message];
exec.mode = [self protobufModeForClientMode:[[SNTConfigurator configurator] clientMode]];
exec.path = @(message.path);
exec.originalPath = [self originalPathForTranslocation:&message];
exec.argsArray = [(__bridge NSArray *)message.args_array mutableCopy];
exec.machineId =
[[SNTConfigurator configurator] enableMachineIDDecoration] ? self.machineID : nil;
[self logWithSantaMessage:&message
wrapper:^(SNTPBSantaMessage *sm) {
sm.execution = exec;
}];
}
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message {
[self logExecution:message withDecision:cd];
}
- (void)logAllowedExecution:(santa_message_t)message {
SNTCachedDecision *cd = [self cachedDecisionForMessage:message];
[self logExecution:message withDecision:cd];
// We also reset the timestamp for transitive rules here, because it happens to be where we
// have access to both the execution notification and the sha256 associated with rule.
[self resetTimestampForCachedDecision:cd];
}
- (void)logDiskAppeared:(NSDictionary *)diskProperties {
NSString *dmgPath = nil;
NSString *serial = nil;
if ([diskProperties[@"DADeviceModel"] isEqual:@"Disk Image"]) {
dmgPath = [self diskImageForDevice:diskProperties[@"DADevicePath"]];
} else {
serial = [self serialForDevice:diskProperties[@"DADevicePath"]];
serial = [serial stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
NSString *model = [NSString stringWithFormat:@"%@ %@", diskProperties[@"DADeviceVendor"] ?: @"",
diskProperties[@"DADeviceModel"] ?: @""];
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *appearanceDateString = [self.dateFormatter
stringFromDate:[NSDate
dateWithTimeIntervalSinceReferenceDate:[diskProperties[@"DAAppearanceTime"]
doubleValue]]];
SNTPBDiskAppeared *diskAppeared = [[SNTPBDiskAppeared alloc] init];
diskAppeared.mount = [diskProperties[@"DAVolumePath"] path];
diskAppeared.volume = diskProperties[@"DAVolumeName"];
diskAppeared.bsdName = diskProperties[@"DAMediaBSDName"];
diskAppeared.fs = diskProperties[@"DAVolumeKind"];
diskAppeared.model = model;
diskAppeared.serial = serial;
diskAppeared.bus = diskProperties[@"DADeviceProtocol"];
diskAppeared.dmgPath = dmgPath;
diskAppeared.appearance = appearanceDateString;
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
sm.diskAppeared = diskAppeared;
}];
}
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {
SNTPBDiskDisappeared *diskDisappeared = [[SNTPBDiskDisappeared alloc] init];
diskDisappeared.mount = [diskProperties[@"DAVolumePath"] path];
diskDisappeared.volume = diskProperties[@"DAVolumeName"];
diskDisappeared.bsdName = diskProperties[@"DAMediaBSDName"];
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
sm.diskDisappeared = diskDisappeared;
}];
}
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events {
for (SNTStoredEvent *event in events) {
SNTPBBundle *bundle = [[SNTPBBundle alloc] init];
bundle.sha256 = event.fileSHA256;
bundle.bundleHash = event.fileBundleHash;
bundle.bundleName = event.fileBundleName;
bundle.bundleId = event.fileBundleID;
bundle.bundlePath = event.fileBundlePath;
bundle.path = event.filePath;
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
sm.bundle = bundle;
}];
}
}
- (void)logFork:(santa_message_t)message {
SNTPBFork *forkEvent = [[SNTPBFork alloc] init];
forkEvent.processInfo = [self protobufProcessInfoForSantaMessage:&message];
[self logWithSantaMessage:&message
wrapper:^(SNTPBSantaMessage *sm) {
sm.fork = forkEvent;
}];
}
- (void)logExit:(santa_message_t)message {
SNTPBExit *exitEvent = [[SNTPBExit alloc] init];
exitEvent.processInfo = [self protobufProcessInfoForSantaMessage:&message];
[self logWithSantaMessage:&message
wrapper:^(SNTPBSantaMessage *sm) {
sm.exit = exitEvent;
}];
}
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo {
SNTPBAllowlist *allowlistEvent = [[SNTPBAllowlist alloc] init];
allowlistEvent.pid = allowlistInfo.pid;
allowlistEvent.pidversion = allowlistInfo.pidversion;
allowlistEvent.path = allowlistInfo.targetPath;
allowlistEvent.sha256 = allowlistInfo.sha256;
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
sm.allowlist = allowlistEvent;
}];
}
@end

View File

@@ -0,0 +1,438 @@
/// 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 <EndpointSecurity/EndpointSecurity.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import <grp.h>
#import <pwd.h>
#import "Source/common/SNTAllowlistInfo.h"
#import "Source/common/SNTCachedDecision.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santad/EventProviders/EndpointSecurityTestUtil.h"
#import "Source/santad/Logs/SNTProtobufEventLog.h"
#import "Source/santad/Logs/SNTSimpleMaildir.h"
@interface SNTProtobufEventLogTest : XCTestCase
@property id mockConfigurator;
@property id mockLogOutput;
@end
@interface SNTProtobufEventLog (Testing)
- (instancetype)initWithLog:(id<SNTLogOutput>)log;
@end
NSString *getBestBundleName(NSBundle *bundle) {
NSDictionary *infoDict = [bundle infoDictionary];
return [[infoDict objectForKey:@"CFBundleDisplayName"] description]
?: [[infoDict objectForKey:@"CFBundleName"] description];
}
SNTStoredEvent *createTestBundleStoredEvent(NSBundle *bundle, NSString *fakeBundleHash,
NSString *fakeFileHash) {
if (!bundle) {
return nil;
}
SNTStoredEvent *event = [[SNTStoredEvent alloc] init];
event.idx = @(arc4random());
event.fileSHA256 = fakeFileHash;
event.fileBundleHash = fakeBundleHash;
event.fileBundleName = getBestBundleName(bundle);
event.fileBundleID = bundle.bundleIdentifier;
event.filePath = bundle.executablePath;
event.fileBundlePath = bundle.bundlePath;
return event;
}
id getEventForMessage(SNTPBSantaMessage *santaMsg, SNTPBSantaMessage_Message_OneOfCase expectedCase,
NSString *propertyName, Class expectedClass) {
if (santaMsg.messageOneOfCase != expectedCase) {
LOGE(@"Unexpected message type. Had: %d, Expected: %d", santaMsg.messageOneOfCase,
expectedCase);
return nil;
}
id event = [santaMsg valueForKey:propertyName];
XCTAssertTrue([event isKindOfClass:expectedClass], "Extracted unexpected class");
return event;
}
NSBundle *getBundleForSystemApplication(NSString *appName) {
if (@available(macOS 10.15, *)) {
return
[NSBundle bundleWithPath:[NSString stringWithFormat:@"/System/Applications/%@", appName]];
} else {
return [NSBundle bundleWithPath:[NSString stringWithFormat:@"/Applications/%@", appName]];
}
}
void assertProcessInfoMatchesExpected(SNTPBProcessInfo *procInfo, const santa_message_t *expected) {
XCTAssertTrue(procInfo.pid == expected->pid);
XCTAssertTrue(procInfo.pidversion == expected->pidversion);
XCTAssertTrue(procInfo.ppid == expected->ppid);
XCTAssertTrue(procInfo.uid == expected->uid);
XCTAssertTrue(procInfo.gid == expected->gid);
XCTAssertTrue([procInfo.user isEqualToString:@(user_from_uid(expected->uid, 0))]);
XCTAssertTrue([procInfo.group isEqualToString:@(group_from_gid(expected->gid, 0))]);
}
// Creates a basic santa message with only process-related info filled out.
// Adding path data is left as an exercise to the caller.
santa_message_t getBasicSantaMessage(santa_action_t action) {
santa_message_t santaMsg = {0};
santaMsg.action = action;
santaMsg.uid = 242;
santaMsg.gid = 20;
santaMsg.pid = arc4random() % 1000;
santaMsg.pidversion = arc4random() % 1000;
santaMsg.ppid = arc4random() % 1000;
return santaMsg;
}
@implementation SNTProtobufEventLogTest
- (void)setUp {
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
OCMStub([self.mockConfigurator enableMachineIDDecoration]).andReturn(NO);
self.mockLogOutput = OCMStrictProtocolMock(@protocol(SNTLogOutput));
}
- (void)tearDown {
[self.mockConfigurator stopMocking];
[self.mockLogOutput stopMocking];
}
- (void)testLogFileModification {
NSString *processName = @"launchd";
NSString *processPath = @"/sbin/launchd";
NSString *sourcePath = @"/foo/bar.txt";
NSString *targetPath = @"/bar/foo.txt";
struct timespec ts = {123, 456};
// Create a test ES message with some important data set
es_file_t esFile = MakeESFile([processPath UTF8String]);
es_process_t esProc = MakeESProcess(&esFile);
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_RENAME, &esProc, ts);
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_RENAME);
strlcpy(santaMsg.path, [sourcePath UTF8String], sizeof(santaMsg.path));
strlcpy(santaMsg.newpath, [targetPath UTF8String], sizeof(santaMsg.newpath));
strlcpy(santaMsg.pname, [processName UTF8String], sizeof(santaMsg.pname));
santaMsg.args_array = nil;
santaMsg.es_message = &esMsg;
OCMExpect([self.mockLogOutput logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
// Note: Only checking seconds because nano conversion can drift
// slightly due to double precision.
if (sm.eventTime.seconds != ts.tv_sec) {
LOGE(@"Unexpected message event time");
return NO;
}
SNTPBFileModification *fileMod = getEventForMessage(
sm, SNTPBSantaMessage_Message_OneOfCase_FileModification,
@"fileModification", [SNTPBFileModification class]);
if (fileMod.action != SNTPBFileModification_Action_ActionRename ||
![fileMod.path isEqualToString:sourcePath] ||
![fileMod.newpath isEqualToString:targetPath] ||
![fileMod.process isEqualToString:processName] ||
![fileMod.processPath isEqualToString:processPath] ||
!fileMod.hasProcessInfo || fileMod.processInfo == nil ||
[fileMod.machineId length] != 0) {
LOGE(@"Unexpected file modification data");
return NO;
}
assertProcessInfoMatchesExpected(fileMod.processInfo, &santaMsg);
return YES;
}]]);
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
[eventLog logFileModification:santaMsg];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
}
- (void)testLogExecutionDenied {
NSString *processName = @"launchd";
NSString *processPath = @"/sbin/launchd";
NSArray *execArgs = @[ @"/sbin/launchd", @"--init", @"--testing" ];
NSString *explanation = @"explanation";
NSString *sha256 = @"a4587ab1e705a3804fd23c387a7bc1b39505f699eca35f57687809f8a7031d0f";
NSString *certSHA256 = @"293d4a40b539dfddf9e011fb0e37f19aa86c96aad2e4bf481aac9e50487f3868";
NSString *commonName = @"my cert common name";
NSString *quarantineURL = @"http://localhost/quarantine";
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_EXEC);
strlcpy(santaMsg.path, [processPath UTF8String], sizeof(santaMsg.path));
santaMsg.newpath[0] = '\0';
strlcpy(santaMsg.pname, [processName UTF8String], sizeof(santaMsg.pname));
santaMsg.args_array = (__bridge void *)execArgs;
santaMsg.es_message = nil;
SNTCachedDecision *cachedDecision = [[SNTCachedDecision alloc] init];
cachedDecision.decision = SNTEventStateBlockTeamID;
cachedDecision.decisionExtra = explanation;
cachedDecision.sha256 = sha256;
cachedDecision.certSHA256 = certSHA256;
cachedDecision.certCommonName = commonName;
cachedDecision.quarantineURL = quarantineURL;
OCMExpect([self.mockLogOutput
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
SNTPBExecution *exec = getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_Execution,
@"execution", [SNTPBExecution class]);
if (exec.decision != SNTPBExecution_Decision_DecisionDeny ||
exec.reason != SNTPBExecution_Reason_ReasonTeamId ||
![exec.explain isEqualToString:explanation] || ![exec.sha256 isEqualToString:sha256] ||
![exec.certSha256 isEqualToString:certSHA256] ||
![exec.certCn isEqualToString:commonName] ||
![exec.quarantineURL isEqualToString:quarantineURL] || !exec.hasProcessInfo ||
exec.processInfo == nil || exec.mode != SNTPBExecution_Mode_ModeLockdown ||
![exec.path isEqualToString:processPath] || [exec.originalPath length] != 0 ||
![exec.argsArray isEqualToArray:execArgs] || [exec.machineId length] != 0) {
LOGE(@"Unexpected execution data");
return NO;
}
assertProcessInfoMatchesExpected(exec.processInfo, &santaMsg);
return YES;
}]]);
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
[eventLog logDeniedExecution:cachedDecision withMessage:santaMsg];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
}
- (void)testLogDiskAppeared {
NSString *mount = @"/mnt/appear";
NSString *volume = @"Macintosh HD";
NSString *bsdName = @"disk0s1";
NSString *kind = @"apfs";
NSString *deviceVendor = @"gCorp";
NSString *deviceModel = @"G1";
NSString *serial = @"fake_serial";
NSString *devicePath = @"IODeviceTree:/";
NSString *deviceProto = @"USB";
NSString *appeared = @"2001-01-01T00:00:00.000Z";
NSDictionary *diskProperties = @{
@"DAVolumePath" : [NSURL URLWithString:mount],
@"DAVolumeName" : volume,
@"DAMediaBSDName" : bsdName,
@"DAVolumeKind" : kind,
@"DADeviceVendor" : deviceVendor,
@"DADeviceModel" : deviceModel,
@"DADevicePath" : devicePath,
@"DADeviceProtocol" : deviceProto,
};
OCMExpect([self.mockLogOutput
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
SNTPBDiskAppeared *diskAppeared =
getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_DiskAppeared, @"diskAppeared",
[SNTPBDiskAppeared class]);
if (![diskAppeared.mount isEqualToString:mount] ||
![diskAppeared.volume isEqualToString:volume] ||
![diskAppeared.bsdName isEqualToString:bsdName] ||
![diskAppeared.fs isEqualToString:kind] ||
![diskAppeared.model
isEqualToString:[NSString stringWithFormat:@"%@ %@", deviceVendor, deviceModel]] ||
![diskAppeared.serial isEqualToString:serial] ||
![diskAppeared.bus isEqualToString:deviceProto] || [diskAppeared.dmgPath length] != 0 ||
![diskAppeared.appearance isEqualToString:appeared]) {
LOGE(@"Unexpected disk appeared data");
return NO;
}
return YES;
}]]);
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
id eventLogMock = OCMPartialMock(eventLog);
OCMExpect([eventLogMock serialForDevice:[OCMArg checkWithBlock:^BOOL(NSString *path) {
return [path isEqualToString:devicePath];
}]])
.andReturn(serial);
[eventLog logDiskAppeared:diskProperties];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput) && OCMVerifyAll(eventLogMock),
"Unable to verify all expectations");
[eventLogMock stopMocking];
}
- (void)testLogDiskDisappeared {
NSString *mount = @"/mnt/disappear";
NSString *volume = @"Macintosh HD";
NSString *bsdName = @"disk0s2";
NSDictionary *diskProperties = @{
@"DAVolumePath" : [NSURL URLWithString:mount],
@"DAVolumeName" : volume,
@"DAMediaBSDName" : bsdName,
};
OCMExpect([self.mockLogOutput logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
SNTPBDiskDisappeared *diskDisappeared = getEventForMessage(
sm, SNTPBSantaMessage_Message_OneOfCase_DiskDisappeared,
@"diskDisappeared", [SNTPBDiskDisappeared class]);
if (![diskDisappeared.mount isEqualToString:mount] ||
![diskDisappeared.volume isEqualToString:volume] ||
![diskDisappeared.bsdName isEqualToString:bsdName]) {
LOGE(@"Unexpected disk disappeared data");
return NO;
}
return YES;
}]]);
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
[eventLog logDiskDisappeared:diskProperties];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
}
- (void)testLogBundleHashingEvents {
NSArray<SNTStoredEvent *> *storedEvents = @[
createTestBundleStoredEvent(getBundleForSystemApplication(@"Calculator.app"), @"abc123",
@"xyz456"),
createTestBundleStoredEvent(getBundleForSystemApplication(@"Calendar.app"), @"123abc",
@"456xyz"),
];
for (SNTStoredEvent *storedEvent in storedEvents) {
OCMExpect([self.mockLogOutput
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
SNTPBBundle *bundleEvent = getEventForMessage(
sm, SNTPBSantaMessage_Message_OneOfCase_Bundle, @"bundle", [SNTPBBundle class]);
if (![bundleEvent.sha256 isEqualToString:storedEvent.fileSHA256] ||
![bundleEvent.bundleHash isEqualToString:storedEvent.fileBundleHash] ||
![bundleEvent.bundleName isEqualToString:storedEvent.fileBundleName] ||
![bundleEvent.bundleId isEqualToString:storedEvent.fileBundleID] ||
![bundleEvent.bundlePath isEqualToString:storedEvent.fileBundlePath] ||
![bundleEvent.path isEqualToString:storedEvent.filePath]) {
LOGE(@"Unexpected bundle event data for: %@", storedEvent.filePath);
return NO;
}
return YES;
}]]);
}
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
[eventLog logBundleHashingEvents:storedEvents];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
}
- (void)testLogFork {
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_FORK);
OCMExpect([self.mockLogOutput
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
SNTPBFork *forkEvent = getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_Fork,
@"fork", [SNTPBFork class]);
if (!forkEvent.hasProcessInfo || forkEvent.processInfo == nil) {
LOGE(@"Unexpected fork data");
return NO;
}
assertProcessInfoMatchesExpected(forkEvent.processInfo, &santaMsg);
return YES;
}]]);
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
[eventLog logFork:santaMsg];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
}
- (void)testLogExit {
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_EXIT);
OCMExpect([self.mockLogOutput
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
SNTPBExit *exitEvent = getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_Exit,
@"exit", [SNTPBExit class]);
if (!exitEvent.hasProcessInfo || exitEvent.processInfo == nil) {
LOGE(@"Unexpected exit data");
return NO;
}
assertProcessInfoMatchesExpected(exitEvent.processInfo, &santaMsg);
return YES;
}]]);
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
[eventLog logExit:santaMsg];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
}
- (void)testLogAllowlist {
SNTAllowlistInfo *allowlistInfo = [[SNTAllowlistInfo alloc] initWithPid:123
pidversion:456
targetPath:@"/sbin/launchd"
sha256:@"abc123"];
OCMExpect([self.mockLogOutput
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
SNTPBAllowlist *allowlistEvent = getEventForMessage(
sm, SNTPBSantaMessage_Message_OneOfCase_Allowlist, @"allowlist", [SNTPBAllowlist class]);
if (allowlistEvent.pid != allowlistInfo.pid ||
allowlistEvent.pidversion != allowlistInfo.pidversion ||
![allowlistEvent.path isEqualToString:allowlistInfo.targetPath] ||
![allowlistEvent.sha256 isEqualToString:allowlistInfo.sha256]) {
LOGE(@"Unexpected allowlist data");
return NO;
}
return YES;
}]]);
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
[eventLog logAllowlist:allowlistInfo];
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
}
@end

View File

@@ -0,0 +1,39 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import "Source/common/Santa.pbobjc.h"
#import "Source/santad/Logs/SNTLogOutput.h"
NS_ASSUME_NONNULL_BEGIN
@interface SNTSimpleMaildir : NSObject<SNTLogOutput>
- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory
filenamePrefix:(NSString *)filenamePrefix
fileSizeThreshold:(size_t)fileSiszeThreshold
directorySizeThreshold:(size_t)directorySizeThreshold
maxTimeBetweenFlushes:(NSTimeInterval)maxTimeBetweenFlushes
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (void)logEvent:(SNTPBSantaMessage *)message;
- (void)flush;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,396 @@
/// 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/santad/Logs/SNTSimpleMaildir.h"
#include <mach/mach_time.h>
#include <malloc/malloc.h>
#include <stdio.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
static NSString *kDefaultMetricFieldName = @"result";
static NSString *kErrorUserInfoKey = @"MetricsFieldName";
/** Helper for creating errors. */
static NSError *MakeError(NSString *description) {
return [NSError errorWithDomain:@"com.google.santa"
code:1
userInfo:@{kErrorUserInfoKey : description}];
}
static NSString *ErrorToMetricFieldName(NSError *error) {
if (!error) {
return kDefaultMetricFieldName;
}
return error.userInfo[kErrorUserInfoKey]
?: [NSString stringWithFormat:@"%@:%d", error.domain, (int)error.code];
}
static size_t SNTRoundUpToNextPage(size_t size) {
const size_t pageSize = 4096;
if (size % pageSize == 0) {
return size;
}
return pageSize * ((size / pageSize) + 1);
}
static uint64_t getUptimeSeconds() {
static dispatch_once_t onceToken;
static mach_timebase_info_data_t info;
dispatch_once(&onceToken, ^{
mach_timebase_info(&info);
});
uint64_t cur = mach_absolute_time();
// Convert from nanoseconds to seconds
return cur * info.numer / (1000 * 1000 * 1000 * info.denom);
}
NS_ASSUME_NONNULL_BEGIN
@implementation SNTSimpleMaildir {
/** The prefix to use for new log files. */
NSString *_filenamePrefix;
/** The base, tmp and new directory for spooling files. */
NSString *_baseDirectory;
NSString *_tmpDirectory;
NSString *_newDirectory;
/**
* Timer that flushes every `maxTimeBetweenFlushes` seconds.
* Used to avoid excessive latency exporting events.
*/
NSTimer *_flushTimer;
/** The size threshold after which to start a new log file. */
size_t _fileSizeThreshold;
/** Threshold for the estimated spool size. */
size_t _spoolSizeThreshold;
/** Temporary storage for SNTPBSantaMessage in an SNTPBLogBatch. */
SNTPBLogBatch *_outputProto;
/** Current serialized size of all events in the _outputProto batch */
size_t _outputProtoSerializedSize;
/** Current size of the file system spooling directory. */
size_t _estimatedSpoolSize;
/** Counter for the files we've already opened. Used to generate file names. */
int _createdFileCount;
/** Dispatch queue to synchronize flush operations */
dispatch_queue_t _flushQueue;
/** Counter for successful and failed event flushing to disk. */
SNTMetricCounter *_eventsFlushedCounter;
/** Counter for successful and failed event queueing in memory. */
SNTMetricCounter *_eventsQueuedCounter;
/** Gauge for the overall spool size, calculated periodically. */
SNTMetricInt64Gauge *_spoolSizeGauge;
/**
* Mach absolute time in seconds of the last time the spool size
* was calculated.
*/
uint64_t _lastCalculatedSpoolSizeTime;
}
- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory
filenamePrefix:(NSString *)filenamePrefix
fileSizeThreshold:(size_t)fileSizeThreshold
directorySizeThreshold:(size_t)directorySizeThreshold
maxTimeBetweenFlushes:(NSTimeInterval)maxTimeBetweenFlushes {
self = [super init];
if (self) {
_baseDirectory = baseDirectory;
_tmpDirectory = [baseDirectory stringByAppendingPathComponent:@"tmp"];
_newDirectory = [baseDirectory stringByAppendingPathComponent:@"new"];
_filenamePrefix = [filenamePrefix copy];
_fileSizeThreshold = fileSizeThreshold;
_spoolSizeThreshold = directorySizeThreshold;
_estimatedSpoolSize = SIZE_T_MAX; // Force a recalculation of the spool directory size
_createdFileCount = 0;
_outputProto = [[SNTPBLogBatch alloc] init];
_outputProtoSerializedSize = 0;
_lastCalculatedSpoolSizeTime = 0;
_eventsFlushedCounter = [[SNTMetricSet sharedInstance]
counterWithName:@"/santa/events_flushed"
fieldNames:@[ kDefaultMetricFieldName ]
helpText:@"Number of events flushed, with the result of the flush operation"];
_eventsQueuedCounter = [[SNTMetricSet sharedInstance]
counterWithName:@"/santa/events_queued"
fieldNames:@[ kDefaultMetricFieldName ]
helpText:@"Number of events queued in memory, with the result "
@"of their conversion to anyproto"];
_spoolSizeGauge =
[[SNTMetricSet sharedInstance] int64GaugeWithName:@"/santa/spool_size"
fieldNames:@[ kDefaultMetricFieldName ]
helpText:@"Snapshot of the current pool size"];
[[SNTMetricSet sharedInstance] registerCallback:^(void) {
// Only calculate spool size for metrics every 5 minutes
static const int frequencySecs = 300;
uint64_t curTime = getUptimeSeconds();
if (curTime - _lastCalculatedSpoolSizeTime >= frequencySecs) {
NSError *err = nil;
size_t curSize = [SNTSimpleMaildir spoolDirectorySize:_newDirectory withError:&err];
if (err) {
// Failed to calculate spool size. Try again next time...
return;
}
_lastCalculatedSpoolSizeTime = curTime;
[_spoolSizeGauge set:curSize forFieldValues:@[ kDefaultMetricFieldName ]];
}
}];
_flushQueue =
dispatch_queue_create("com.google.santa.daemon.mail",
dispatch_queue_attr_make_with_qos_class(
DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL, QOS_CLASS_DEFAULT, 0));
typeof(self) __weak weakSelf = self;
_flushTimer = [NSTimer scheduledTimerWithTimeInterval:maxTimeBetweenFlushes
repeats:YES
block:^(NSTimer *_Nonnull timer) {
[weakSelf flush];
}];
}
return self;
}
- (void)dealloc {
[_flushTimer invalidate];
[self flush];
}
/** Fires the flush timer programmatically. Used for testing purposes. */
- (void)fireFlushTimer {
[_flushTimer fire];
}
/**
* Flushes out buffered data.
*
* Returns the number of events that we attempted to flush, and populates the error if that flush
* failed.
*/
- (void)flushLockedWithError:(NSError **)error {
NSAssert([_outputProto.recordsArray count] < INT_MAX, @"Too many records");
if ([_outputProto.recordsArray count] == 0) {
return;
}
if (![self createSpoolDirectoriesWithError:error]) {
return;
}
if (_estimatedSpoolSize > _spoolSizeThreshold) {
_estimatedSpoolSize = [SNTSimpleMaildir spoolDirectorySize:_newDirectory withError:error];
if (_estimatedSpoolSize > _spoolSizeThreshold) {
if (error) {
*error = MakeError(@"file_system_threshold_exceeded");
}
return;
}
}
NSString *filename = [NSString stringWithFormat:@"%@.%u", _filenamePrefix, _createdFileCount];
NSString *exposedFilepath = [_newDirectory stringByAppendingPathComponent:filename];
NSString *outputFilepath = [_tmpDirectory stringByAppendingPathComponent:filename];
NSOutputStream *outputFile = [NSOutputStream outputStreamToFileAtPath:outputFilepath append:NO];
[outputFile open];
_createdFileCount++;
if (!outputFile) {
if (error) {
*error = MakeError(@"data_loss_on_open");
}
return;
}
BOOL writeSuccess = NO;
@try {
[_outputProto writeToOutputStream:outputFile];
writeSuccess = YES;
} @catch (NSException *exception) {
NSLog(@"Error while writing to %@: %@", outputFilepath, exception);
if (error) {
*error = MakeError(@"data_loss_on_write");
}
}
[outputFile close];
if (!writeSuccess) {
// Unable to successfully write all data.
[[NSFileManager defaultManager] removeItemAtPath:outputFilepath error:nil];
return;
}
if (![[NSFileManager defaultManager] moveItemAtPath:outputFilepath
toPath:exposedFilepath
error:nil]) {
// Delete the tmp file if unable to move
[[NSFileManager defaultManager] removeItemAtPath:outputFilepath error:nil];
if (error) {
*error = MakeError(@"data_loss_on_move");
}
}
if (error && !*error) {
_estimatedSpoolSize += _outputProtoSerializedSize;
}
return;
}
- (void)flushAndUpdateCountersLocked {
NSError *error = nil;
[self flushLockedWithError:&error];
[_eventsFlushedCounter incrementBy:[_outputProto.recordsArray count]
forFieldValues:@[ ErrorToMetricFieldName(error) ]];
// Clear output buffer.
_outputProto = [[SNTPBLogBatch alloc] init];
_outputProtoSerializedSize = 0;
}
- (void)flush {
dispatch_sync(_flushQueue, ^{
[self flushAndUpdateCountersLocked];
});
}
- (BOOL)createDirectory:(NSString *)dir withError:(NSError **)error {
BOOL isDir;
// Check if the path exists
if (![[NSFileManager defaultManager] fileExistsAtPath:dir isDirectory:&isDir]) {
// If path doesn't exist, attempt to create it
if (![[NSFileManager defaultManager] createDirectoryAtPath:dir
withIntermediateDirectories:YES
attributes:nil
error:nil]) {
if (error) {
*error = MakeError(@"failed_to_create_dir");
}
return NO;
}
} else if (!isDir) {
// The path existed, but it wasn't a directory
if (error) {
*error = MakeError(@"path_exists_not_directory");
}
return NO;
}
// If we made it here, the directory was created or already existed
return YES;
}
- (BOOL)createSpoolDirectoriesWithError:(NSError **)error {
return [self createDirectory:_baseDirectory withError:error] &&
[self createDirectory:_tmpDirectory withError:error] &&
[self createDirectory:_newDirectory withError:error];
}
+ (size_t)spoolDirectorySize:(NSString *)newDirectory withError:(NSError **)error {
size_t totalSize = 0;
NSFileManager *fm = [NSFileManager defaultManager];
NSError *enumerationError = nil;
NSArray<NSString *> *filenames = [fm contentsOfDirectoryAtPath:newDirectory
error:&enumerationError];
if (enumerationError) {
*error = MakeError(@"spool_dir_enumeration_error");
return 0;
}
for (NSString *filename in filenames) {
NSError *attributesError = nil;
NSDictionary<NSFileAttributeKey, id> *attributes =
[fm attributesOfItemAtPath:[newDirectory stringByAppendingPathComponent:filename]
error:&attributesError];
if (attributesError) {
if (error) {
*error = MakeError(@"spool_dir_attribute_retrieval_error");
}
continue;
}
totalSize += SNTRoundUpToNextPage([attributes fileSize]);
}
return totalSize;
}
- (void)logEvent:(SNTPBSantaMessage *)event {
dispatch_sync(_flushQueue, ^{
// Note: The `serializedSize` method is costly. In order to calculate the
// size of the `_outputProto` accurately, we need to add both the serialized
// size of the new event plus additional overhead incurred from adding the
// new event to the current array of events. The `anyObjOverhead` is used
// to store the calculated overhead when the first event is logged and the
// overhead is then used when calculating `_outputProtoSerializedSize`.
static size_t anyObjOverhead = 0;
static dispatch_once_t onceToken;
if (_outputProtoSerializedSize > _fileSizeThreshold) {
[self flushAndUpdateCountersLocked];
}
NSError *error = nil;
GPBAny *any = [GPBAny anyWithMessage:event error:nil];
if (any) {
[_outputProto.recordsArray addObject:any];
dispatch_once(&onceToken, ^{
size_t outputSerializedSize = [_outputProto serializedSize];
size_t eventSerializedSize = [event serializedSize];
if (outputSerializedSize > eventSerializedSize) {
anyObjOverhead = outputSerializedSize - eventSerializedSize;
}
});
_outputProtoSerializedSize += [event serializedSize] + anyObjOverhead;
} else {
error = MakeError(@"enqueue_error");
}
[_eventsQueuedCounter incrementForFieldValues:@[ ErrorToMetricFieldName(error) ]];
});
}
/** Intentionally left no-op method for this class. */
- (void)logString:(NSString *)logLine {
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -17,10 +17,12 @@
#include <EndpointSecurity/EndpointSecurity.h>
#import <libproc.h>
#import "Source/common/SNTAllowlistInfo.h"
#import "Source/common/SNTCachedDecision.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
@implementation SNTSyslogEventLog
@@ -66,10 +68,11 @@
char ppath[PATH_MAX] = "(null)";
if (message.es_message) {
es_message_t *m = message.es_message;
es_string_token_t path = m->process->executable->path;
strlcpy(ppath, path.data, sizeof(ppath));
[SNTEndpointSecurityManager populateBufferFromESFile:m->process->executable
buffer:ppath
size:sizeof(ppath)];
} else {
proc_pidpath(message.pid, ppath, PATH_MAX);
proc_pidpath(message.pid, ppath, sizeof(ppath));
}
[outStr
@@ -190,14 +193,14 @@
[self sanitizeString:@(message.path)]];
// Check for app translocation by GateKeeper, and log original path if the case.
NSString *originalPath = [self originalPathForTranslocation:message];
NSString *originalPath = [self originalPathForTranslocation:&message];
if (originalPath) {
[outLog appendFormat:@"|origpath=%@", [self sanitizeString:originalPath]];
}
if (logArgs) {
if (message.args_array) {
NSArray *args = CFBridgingRelease(message.args_array);
NSArray *args = (__bridge NSArray *)message.args_array;
[outLog appendFormat:@"|args=%@", [args componentsJoinedByString:@" "]];
} else {
[self addArgsForPid:message.pid toString:outLog];
@@ -286,8 +289,19 @@
[self writeLog:s];
}
- (void)logAllowList:(SNTAllowlistInfo *)allowlistInfo {
[self
writeLog:[NSString stringWithFormat:@"action=ALLOWLIST|pid=%d|pidversion=%d|path=%@|sha256=%@",
allowlistInfo.pid, allowlistInfo.pidversion,
allowlistInfo.targetPath, allowlistInfo.sha256]];
}
- (void)writeLog:(NSString *)log {
LOGI(@"%@", log);
}
- (void)forceFlush {
// Nothing to do
}
@end

View File

@@ -13,27 +13,28 @@
/// limitations under the License.
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCMetricServiceInterface.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTDeviceManager.h"
#import "Source/santad/EventProviders/SNTDriverManager.h"
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
#import "Source/santad/Logs/SNTEventLog.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import "Source/santad/SNTCompilerController.h"
#import "Source/santad/SNTDaemonControlController.h"
#import "Source/santad/SNTDatabaseController.h"
@@ -48,9 +49,9 @@
@property SNTDeviceManager *deviceManager;
@property MOLXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@property MOLXPCConnection *metricsConnection;
@property dispatch_source_t metricsTimer;
@property SNTSyncdQueue *syncdQueue;
@end
@implementation SNTApplication
@@ -60,19 +61,12 @@
if (self) {
SNTConfigurator *configurator = [SNTConfigurator configurator];
// Choose an event logger.
// Locate and connect to driver / SystemExtension
if ([configurator enableSystemExtension]) {
if ([configurator enableSysxCache]) {
LOGI(@"Using CachingEndpointSecurity as event provider.");
_eventProvider = [[SNTCachingEndpointSecurityManager alloc] init];
} else {
LOGI(@"Using EndpointSecurity as event provider.");
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
}
if ([configurator enableSysxCache]) {
LOGI(@"Using CachingEndpointSecurity as event provider.");
_eventProvider = [[SNTCachingEndpointSecurityManager alloc] init];
} else {
LOGI(@"Using Kauth as event provider.");
_eventProvider = [[SNTDriverManager alloc] init];
LOGI(@"Using EndpointSecurity as event provider.");
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
}
if (!_eventProvider) {
@@ -92,12 +86,26 @@
return nil;
}
_deviceManager = [[SNTDeviceManager alloc] init];
self.deviceManager.blockUSBMount = [configurator blockUSBMount];
self.notQueue = [[SNTNotificationQueue alloc] init];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = [configurator blockUSBMount];
if ([configurator remountUSBMode] != nil) {
self.deviceManager.remountArgs = [configurator remountUSBMode];
deviceManager.remountArgs = [configurator remountUSBMode];
}
NSString *deviceBlockMsg = deviceManager.remountArgs != nil
? [configurator remountUSBBlockMessage]
: [configurator bannedUSBBlockMessage];
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
[[self.notQueue.notifierConnection remoteObjectProxy]
postUSBBlockNotification:event
withCustomMessage:deviceBlockMsg];
};
_deviceManager = deviceManager;
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
// The filter is reset when santad disconnects from the driver.
// Add the default filters.
@@ -108,12 +116,7 @@
});
self.notQueue = [[SNTNotificationQueue alloc] init];
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Restart santactl if it goes down
syncdQueue.invalidationHandler = ^{
[self startSyncd];
};
self.syncdQueue = [[SNTSyncdQueue alloc] init];
// Listen for actionable config changes.
NSKeyValueObservingOptions bits = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld);
@@ -150,18 +153,11 @@
options:bits
context:NULL];
if (![configurator enableSystemExtension]) {
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(enableSystemExtension))
options:bits
context:NULL];
}
// Establish XPC listener for Santa and santactl connections
SNTDaemonControlController *dc =
[[SNTDaemonControlController alloc] initWithEventProvider:_eventProvider
notificationQueue:self.notQueue
syncdQueue:syncdQueue];
syncdQueue:self.syncdQueue];
_controlConnection =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceID]];
@@ -179,9 +175,9 @@
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:self.notQueue
syncdQueue:syncdQueue];
// Start up santactl as a daemon if a sync server exists.
[self startSyncd];
syncdQueue:self.syncdQueue];
// Establish a connection with the sync service if a sync server exists.
[self establishSyncServiceConnection];
if (!_execController) return nil;
@@ -314,27 +310,20 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
dispatch_source_cancel(_metricsTimer);
}
- (void)startSyncd {
- (void)establishSyncServiceConnection {
// The syncBaseURL check is here to stop retrying if the sync server is removed.
// See -[syncBaseURLDidChange:] for more info.
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self stopSyncd];
self.syncdPID = fork();
if (self.syncdPID == -1) {
LOGI(@"Failed to fork");
self.syncdPID = 0;
} else if (self.syncdPID == 0) {
// The santactl executable will drop privileges just after the XPC
// connection has been estabilished; this is done this way so that
// the XPC authentication can occur
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
}
LOGI(@"santactl started with pid: %i", self.syncdPID);
}
- (void)stopSyncd {
if (!self.syncdPID) return;
int ret = kill(self.syncdPID, SIGKILL);
LOGD(@"kill(%i, 9) = %i", self.syncdPID, ret);
self.syncdPID = 0;
MOLXPCConnection *ss = [SNTXPCSyncServiceInterface configuredConnection];
// This will handle retying connection establishment if there are issues with the service
// during initialization (missing binary, malformed plist, bad code signature, etc.).
// Once those issues are resolved the connection will establish.
// This will also handle reestablishment if the service crashes or is killed.
ss.invalidationHandler = ^(void) {
[self establishSyncServiceConnection];
};
[ss resume]; // If there are issues establishing the connection resume will block for 2 seconds.
self.syncdQueue.syncConnection = ss;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
@@ -365,15 +354,6 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
LOGI(@"Changed [allow|deny]list regex, flushing cache");
[self.eventProvider flushCacheNonRootOnly:NO];
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(enableSystemExtension))]) {
BOOL new =
[ change[newKey] isKindOfClass : [NSNumber class] ] ? [ change[newKey] boolValue ] : NO;
BOOL old = [change[oldKey] isKindOfClass:[NSNumber class]] ? [change[oldKey] boolValue] : NO;
if (old == NO && new == YES) {
LOGI(@"EnableSystemExtension changed NO -> YES");
LOGI(@"The penultimate exit.");
exit(0);
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(exportMetrics))]) {
BOOL new = [ change[newKey] boolValue ];
BOOL old = [change[oldKey] boolValue];
@@ -428,14 +408,15 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
- (void)syncBaseURLDidChange:(NSURL *)syncBaseURL {
if (syncBaseURL) {
LOGI(@"Starting santactl with new SyncBaseURL: %@", syncBaseURL);
LOGI(@"Establishing a new sync service connection with SyncBaseURL: %@", syncBaseURL);
[NSObject cancelPreviousPerformRequestsWithTarget:[SNTConfigurator configurator]
selector:@selector(clearSyncState)
object:nil];
[self startSyncd];
[[self.syncdQueue.syncConnection remoteObjectProxy] spindown];
[self establishSyncServiceConnection];
} else {
LOGI(@"SyncBaseURL removed, killing santactl pid: %i", self.syncdPID);
[self stopSyncd];
LOGI(@"SyncBaseURL removed, spinning down sync service");
[[self.syncdQueue.syncConnection remoteObjectProxy] spindown];
// Keep the syncState active for 10 min in case com.apple.ManagedClient is flapping.
[[SNTConfigurator configurator] performSelector:@selector(clearSyncState)
withObject:nil

View File

@@ -35,7 +35,6 @@
self.mockSNTDatabaseController = OCMClassMock([SNTDatabaseController class]);
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator enableSystemExtension]).andReturn(true);
OCMStub([self.mockConfigurator enableSysxCache]).andReturn(false);
}

View File

@@ -33,7 +33,6 @@
[super setUp];
fclose(stdout);
self.mockSNTDatabaseController = OCMClassMock([SNTDatabaseController class]);
XCTAssertTrue([[SNTConfigurator configurator] enableSystemExtension]);
}
- (void)tearDown {

View File

@@ -14,16 +14,16 @@
#import <Foundation/Foundation.h>
#import "Source/common/SNTKernelCommon.h"
#import "Source/common/SNTCommon.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
@class SNTDriverManager;
@class SNTEventLog;
@interface SNTCompilerController : NSObject
// Designated initializer takes a SNTEventLog instance so that we can
// call saveDecisionDetails: to create a fake cached decision for transitive
// rule creation requests that are still pending.
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager;
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider;
// Whenever an executable file is closed or renamed whitelist the resulting file.
// We assume that we have already determined that the writing process was a compiler.

View File

@@ -14,10 +14,11 @@
#import "Source/santad/SNTCompilerController.h"
#import "Source/common/SNTAllowlistInfo.h"
#import "Source/common/SNTCachedDecision.h"
#import "Source/common/SNTCommon.h"
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTFileInfo.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
@@ -60,10 +61,10 @@
- (void)createTransitiveRule:(santa_message_t)message {
[self saveFakeDecision:message];
char *target = message.path;
NSString *target = @(message.path);
// Check if this file is an executable.
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:@(target)];
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:target];
if (fi.isExecutable) {
// Check if there is an existing (non-transitive) rule for this file. We leave existing rules
// alone, so that a allowlist or blocklist rule can't be overwritten by a transitive one.
@@ -81,10 +82,10 @@
if (![ruleTable addRules:@[ rule ] cleanSlate:NO error:&err]) {
LOGE(@"unable to add new transitive rule to database: %@", err.localizedDescription);
} else {
[[SNTEventLog logger]
writeLog:[NSString
stringWithFormat:@"action=ALLOWLIST|pid=%d|pidversion=%d|path=%s|sha256=%@",
message.pid, message.pidversion, target, fi.SHA256]];
[[SNTEventLog logger] logAllowlist:[[SNTAllowlistInfo alloc] initWithPid:message.pid
pidversion:message.pidversion
targetPath:target
sha256:fi.SHA256]];
}
}
}

Some files were not shown because too many files have changed in this diff Show More