Compare commits

...

51 Commits

Author SHA1 Message Date
Russell Hancox
a67801d5ed santactl/status: Remove driver connected, re-org USB blocking status (#826) 2022-06-22 14:59:46 -04:00
Russell Hancox
3d37a3a5ae santad: Update assert usage to avoid a string-to-bool conversion (#825) 2022-06-22 12:55:57 -04:00
Russell Hancox
bfae5dc828 fix some style issues (#824) 2022-06-22 10:41:05 -04:00
Pete Markowsky
fde5f52a11 Added handling for Remount events to USB mass storage blocking (#818)
* Added handling for Remount events to USB mass storage blocking.
2022-06-22 09:39:20 -04:00
Russell Hancox
01bd1bfdca santad: Use multiple semaphores to avoid freeing ES message before use of it has ended. (#822)
This slightly complex solution is necessary because while on macOS 11+ there are retain/release methods that can be used on ES messages, on 10.15 the only option is a copy which is comparatively expensive (and on 11+ the copy/free methods are aliases for retain/release)

Fixes #821
2022-06-08 11:21:40 -04:00
Matt W
ae13900676 Mute self to reduce message volume. Remove noisy log message. (#820)
* Mute self to reduce message volume. Remove noisy log message.

* Bail if self muting failed. Remove selfPid.

* Fix tests by mocking es_mute_process
2022-05-31 11:36:35 -04:00
Matt W
a65c91874b Copy new PrinterProxy file instead of overwriting (#819)
* Copy new PrinterProxy file instead of overwriting

* Update log type for error message

* Update log message severity
2022-05-27 13:08:25 -04:00
Matt W
6a3fda069c Remove unused testing scripts (#816)
* Remvoe unused testing scripts

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2022-05-27 11:03:10 -04:00
Khalid Jamal Abdulnasser
4d34099142 santad: log decision when failing to read file (#817) 2022-05-27 09:52:06 -04:00
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
90 changed files with 2013 additions and 1207 deletions

1
.bazelversion Normal file
View File

@@ -0,0 +1 @@
5.0.0

View File

@@ -53,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'
@@ -66,14 +66,14 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
os: [macos-10.15, macos-11, macos-12]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: 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
@@ -87,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:

19
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,13 +10,14 @@ exports_files(["LICENSE"])
# The version label for mac_* rules.
apple_bundle_version(
name = "version",
build_label_pattern = "{build}",
build_version = SANTA_VERSION + ".{build}",
build_label_pattern = ".*santa_{release}\\.{build}",
build_version = "{release}.{build}",
capture_groups = {
"release": "\\d{4}\\.\\d+",
"build": "\\d+",
},
fallback_build_label = "1",
short_version_string = SANTA_VERSION,
fallback_build_label = "santa_9999.1.1",
short_version_string = "{release}",
)
# Used to detect release builds
@@ -54,6 +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 launchctl unload /Library/LaunchDaemons/com.google.santa.syncservice.plist 2>/dev/null
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
""",
)
@@ -64,6 +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
""",
)
@@ -98,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",
@@ -107,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."
@@ -149,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

View File

@@ -48,6 +48,7 @@ readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.s
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
@@ -64,7 +65,7 @@ readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
EN="${ENTITLEMENTS}/${BN}.entitlements"
@@ -113,11 +114,11 @@ echo "verifying signatures"
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
echo "creating fresh release tarball"
/bin/mkdir -p "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/binaries" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/conf" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/dsym" "${RELEASE_ROOT}/${RELEASE_NAME}"
/usr/bin/tar -C "${RELEASE_ROOT}" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
/bin/mkdir -p "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/binaries" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/conf" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/dsym" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/usr/bin/tar -C "${SCRATCH}/tar_root" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
echo "creating app pkg"
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
@@ -130,6 +131,7 @@ echo "creating app pkg"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.syncservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"

View File

@@ -26,6 +26,9 @@ mkdir -p /usr/local/bin
# Load com.google.santa.metricservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load com.google.santa.syncservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.syncservice.plist
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
[[ -z "${GUI_USER}" ]] && exit 0

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,7 +27,10 @@ fi
# Unload metric service
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
# Unload kext.
# Unload sync service
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Determine if anyone is logged into the GUI
@@ -59,6 +62,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
@@ -74,6 +78,9 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Load com.google.santa.metricservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load com.google.santa.syncservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
# Load GUI agent if someone is logged in.
[[ -z "${GUI_USER}" ]] && exit 0

View File

@@ -13,6 +13,7 @@
# remove helper XPC services
/bin/launchctl remove com.google.santa.bundleservice
/bin/launchctl remove com.google.santa.metricservice
/bin/launchctl remove com.google.santa.syncservice
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
user=$(/usr/bin/stat -f '%u' /dev/console)
@@ -28,6 +29,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.syncservice.plist
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
/bin/rm -f /usr/local/bin/santactl # just a symlink

View File

@@ -1,7 +1,7 @@
# 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

View File

@@ -3,13 +3,12 @@ load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
)
licenses(["notice"])
proto_library(
name = "log_proto",
name = "santa_proto",
srcs = ["santa.proto"],
deps = [
"@com_google_protobuf//:any_proto",
@@ -18,16 +17,15 @@ proto_library(
)
objc_proto_library(
name = "log_objc_proto",
name = "santa_objc_proto",
copts = ["-fno-objc-arc"],
non_arc_srcs = ["Santa.pbobjc.m"],
protos = [":log_proto"],
protos = [":santa_proto"],
)
cc_library(
name = "SantaCache",
hdrs = ["SantaCache.h"],
features = ["layering_check"],
deps = ["//Source/common:SNTCommon"],
)
@@ -48,15 +46,7 @@ objc_library(
":SNTConfigurator",
":SNTLogging",
":SNTStoredEvent",
],
)
objc_library(
name = "SNTDeviceEvent",
srcs = ["SNTDeviceEvent.m"],
hdrs = ["SNTDeviceEvent.h"],
deps = [
":SNTCommonEnums",
":SNTSystemInfo",
],
)
@@ -70,6 +60,7 @@ objc_library(
":SNTDeviceEvent",
":SNTLogging",
":SNTStoredEvent",
":SNTSystemInfo",
],
)
@@ -83,6 +74,15 @@ objc_library(
],
)
objc_library(
name = "SNTDeviceEvent",
srcs = ["SNTDeviceEvent.m"],
hdrs = ["SNTDeviceEvent.h"],
deps = [
":SNTCommonEnums",
],
)
objc_library(
name = "SNTAllowlistInfo",
srcs = ["SNTAllowlistInfo.m"],
@@ -128,7 +128,6 @@ cc_library(
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
features = ["layering_check"],
)
objc_library(
@@ -143,7 +142,6 @@ cc_library(
srcs = ["SNTPrefixTree.cc"],
hdrs = ["SNTPrefixTree.h"],
copts = ["-std=c++11"],
features = ["layering_check"],
deps = [":SNTLogging"],
)
@@ -167,7 +165,6 @@ objc_library(
cc_library(
name = "SNTStrengthify",
hdrs = ["SNTStrengthify.h"],
features = ["layering_check"],
)
objc_library(
@@ -201,9 +198,12 @@ objc_library(
srcs = ["SNTXPCControlInterface.m"],
hdrs = ["SNTXPCControlInterface.h"],
deps = [
":SNTCommonEnums",
":SNTConfigurator",
":SNTRule",
":SNTStoredEvent",
":SNTXPCUnprivilegedControlInterface",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
@@ -225,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",
],

View File

@@ -119,9 +119,16 @@
if (!formatStr.length) return nil;
if (event.fileSHA256) {
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_identifier%"
withString:event.fileSHA256];
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%bundle_or_file_identifier%"
withString:event.fileBundleHash ?: event.fileSHA256];
}
if (event.executingUser) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"

View File

@@ -26,8 +26,6 @@
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// Enum defining actions that can be passed down the IODataQueue and in
// response methods.
typedef enum {
ACTION_UNSET = 0,
@@ -74,15 +72,9 @@ typedef struct santa_vnode_id_t {
bool operator==(const santa_vnode_id_t &rhs) const {
return fsid == rhs.fsid && fileid == rhs.fileid;
}
// This _must not_ be used for anything security-sensitive. It exists solely
// to make the msleep/wakeup calls easier.
uint64_t unsafe_simple_id() const {
return (((uint64_t)fsid << 32) | fileid);
}
#endif
} santa_vnode_id_t;
// Message struct that is sent down the IODataQueue.
typedef struct {
santa_action_t action;
santa_vnode_id_t vnode_id;
@@ -93,18 +85,17 @@ typedef struct {
pid_t ppid;
char path[MAXPATHLEN];
char newpath[MAXPATHLEN];
char ttypath[MAXPATHLEN];
// For file events, this is the process name.
// For exec requests, this is the parent process name.
// While process names can technically be 4*MAXPATHLEN, that never
// actually happens, so only take MAXPATHLEN and throw away any excess.
char pname[MAXPATHLEN];
// For messages that originate from EndpointSecurity, this points to a copy of
// the message.
// This points to a copy of the original ES message.
void *es_message;
// For messages that originate from EndpointSecurity, this points to an
// NSArray of the arguments.
// This points to an NSArray of the process arguments.
void *args_array;
} santa_message_t;

View File

@@ -93,6 +93,22 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
SNTEventLogTypeSyslog,
SNTEventLogTypeFilelog,
SNTEventLogTypeProtobuf,
SNTEventLogTypeNull,
};
// The return status of a sync.
typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
SNTSyncStatusTypeSuccess,
SNTSyncStatusTypePreflightFailed,
SNTSyncStatusTypeEventUploadFailed,
SNTSyncStatusTypeRuleDownloadFailed,
SNTSyncStatusTypePostflightFailed,
SNTSyncStatusTypeTooManySyncsInProgress,
SNTSyncStatusTypeMissingSyncBaseURL,
SNTSyncStatusTypeMissingMachineID,
SNTSyncStatusTypeDaemonTimeout,
SNTSyncStatusTypeSyncStarted,
SNTSyncStatusTypeUnknown,
};
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {

View File

@@ -151,9 +151,10 @@
///
/// Defines how event logs are stored. Options are:
/// SNTEventLogTypeSyslog: Sent to ASL or ULS (if built with the 10.12 SDK or later).
/// SNTEventLogTypeFilelog: Sent to a file on disk. Use eventLogPath to specify a path.
/// SNTEventLogTypeProtobuf: (BETA) Sent to a file on disk, using maildir format. Use
/// SNTEventLogTypeSyslog "syslog": Sent to ASL or ULS (if built with the 10.12 SDK or later).
/// SNTEventLogTypeFilelog "file": Sent to a file on disk. Use eventLogPath to specify a path.
/// SNTEventLogTypeNull "null": Logs nothing
/// SNTEventLogTypeProtobuf "protobuf": (BETA) Sent to a file on disk, using maildir format. Use
/// mailDirectory to specify a path. Use mailDirectoryFileSizeThresholdKB,
/// mailDirectorySizeThresholdMB and mailDirectoryEventMaxFlushTimeSec to configure
/// additional maildir format settings.
@@ -411,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.
///

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";
@@ -111,6 +112,7 @@ 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";
@@ -146,7 +148,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kRemountUSBModeKey : array,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
kSyncCleanRequired : number,
kEnableAllEventUploadKey : number,
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
@@ -207,6 +210,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kMetricExportInterval : number,
kMetricExportTimeout : number,
kMetricExtraLabels : dictionary,
kEnableAllEventUploadKey : number,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -394,6 +398,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableAllEventUpload {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
return [self configStateSet];
}
@@ -693,6 +701,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return SNTEventLogTypeProtobuf;
} else if ([logType isEqualToString:@"syslog"]) {
return SNTEventLogTypeSyslog;
} else if ([logType isEqualToString:@"null"]) {
return SNTEventLogTypeNull;
} else if ([logType isEqualToString:@"file"]) {
return SNTEventLogTypeFilelog;
} else {
return SNTEventLogTypeFilelog;
}
@@ -734,6 +746,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : YES;
}
- (BOOL)enableCleanSyncEventUpload {
NSNumber *number = self.configState[kSyncEnableCleanSyncEventUpload];
return number ? [number boolValue] : NO;
}
- (BOOL)enableAllEventUpload {
NSNumber *n = self.syncState[kEnableAllEventUploadKey];
if (n) return [n boolValue];
return [self.configState[kEnableAllEventUploadKey] boolValue];
}
- (void)setEnableAllEventUpload:(BOOL)enabled {
[self updateSyncStateForKey:kEnableAllEventUploadKey value:@(enabled)];
}
- (BOOL)enableForkAndExitLogging {
NSNumber *number = self.configState[kEnableForkAndExitLogging];
return number ? [number boolValue] : NO;

View File

@@ -49,6 +49,9 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
/// Get the logging level for this process.
LogLevel EffectiveLogLevel();
#ifdef __cplusplus
} // extern C
#endif

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

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

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

@@ -1,32 +0,0 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTStoredEvent.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
argumentIndex:0
ofReply:NO];
return r;
}
@end

View File

@@ -28,12 +28,11 @@
@protocol SNTUnprivilegedDaemonControlXPC
///
/// Kernel ops
/// Cache Ops
///
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
///
/// Database ops

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",
])
@@ -36,13 +40,18 @@ objc_library(
"IOKit",
"SecurityInterface",
"SystemExtensions",
"UserNotifications",
],
deps = [
"//Source/common:SNTBlockMessage_SantaGUI",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCNotifierInterface",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
@@ -54,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/**"]),
@@ -66,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="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,7 +17,7 @@
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -15,7 +15,7 @@
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="515" height="318"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="515" height="318"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -21,13 +21,13 @@
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" misplaced="YES" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="540" height="462"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="16" y="451" width="37" height="32"/>
<rect key="frame" x="16" y="434" width="37" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@@ -240,17 +240,16 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="153" y="35" width="114" height="23"/>
<rect key="frame" x="147" y="30" width="126" height="32"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<buttonCell key="cell" type="push" title="Open Event..." bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
@@ -285,18 +284,17 @@ DQ
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="277" y="33" width="112" height="25"/>
<rect key="frame" x="271" y="28" width="124" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Ignore" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<buttonCell key="cell" type="push" title="Ignore" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
Gw
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<accessibility description="Dismiss Dialog"/>
<connections>

View File

@@ -28,7 +28,8 @@
}
- (NSString *)messageHash {
return @"";
[self doesNotRecognizeSelector:_cmd];
return nil;
}
@end

View File

@@ -15,6 +15,7 @@
#import "Source/santa/SNTNotificationManager.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <UserNotifications/UserNotifications.h>
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
@@ -58,7 +59,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
if ([self.pendingNotifications count]) {
if (self.pendingNotifications.count) {
[self showQueuedWindow];
} else {
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
@@ -83,9 +84,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
for (SNTMessageWindowController *msg in self.pendingNotifications) {
if ([msg messageHash] == [pendingMsg messageHash]) {
return YES;
}
if ([[msg messageHash] isEqual:[pendingMsg messageHash]]) return YES;
}
return NO;
}
@@ -156,6 +155,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
// Otherwise abandon bundle hashing and display the blockable event.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[withController updateBlockNotification:event withBundleHash:nil];
LOGE(@"Timeout connecting to bundle service");
return;
}
@@ -208,28 +208,57 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
NSString *customMsg;
UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Santa";
switch (clientmode) {
case SNTClientModeMonitor:
un.informativeText = @"Switching into Monitor mode";
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
case SNTClientModeMonitor: {
content.body = @"Switching into Monitor mode";
NSString *customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
if (!customMsg) break;
// If a custom message is added but as an empty string, disable notifications.
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
content.body = [SNTBlockMessage stringFromHTML:customMsg];
break;
case SNTClientModeLockdown:
un.informativeText = @"Switching into Lockdown mode";
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
}
case SNTClientModeLockdown: {
content.body = @"Switching into Lockdown mode";
NSString *customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
if (!customMsg) break;
// If a custom message is added but as an empty string, disable notifications.
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
content.body = [SNTBlockMessage stringFromHTML:customMsg];
break;
}
default: return;
}
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
UNNotificationRequest *req =
[UNNotificationRequest requestWithIdentifier:@"clientModeNotification"
content:content
trigger:nil];
[un addNotificationRequest:req withCompletionHandler:nil];
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
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 {
@@ -244,14 +273,6 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
[self queueMessage:pendingMsg];
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
un.informativeText = message ?: @"Requested application can now be run";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");

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

@@ -5,7 +5,6 @@ licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
)
objc_library(
@@ -49,9 +48,11 @@ objc_library(
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"//Source/santasyncservice:sync_lib",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
@@ -66,10 +67,10 @@ macos_command_line_application(
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
"//conditions:default": "//profiles:santa_dev",
}),
version = "//:version",
deps = [":santactl_lib"],
@@ -89,6 +90,7 @@ santa_unit_test(
"//Source/common:SNTCachedDecision",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTXPCControlInterface",
"@MOLCertificate",
"@MOLCodesignChecker",
@@ -99,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

@@ -48,16 +48,10 @@ REGISTER_COMMAND_NAME(@"status")
dispatch_group_t group = dispatch_group_create();
// Daemon status
__block BOOL driverConnected;
__block NSString *clientMode;
__block uint64_t cpuEvents, ramEvents;
__block double cpuPeak, ramPeak;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] driverConnectionEstablished:^(BOOL connected) {
driverConnected = connected;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
switch (cm) {
case SNTClientModeMonitor: clientMode = @"Monitor"; break;
@@ -82,7 +76,7 @@ REGISTER_COMMAND_NAME(@"status")
BOOL cachingEnabled = [configurator enableSysxCache];
// Kext status
// Cache status
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
if (cachingEnabled) {
dispatch_group_enter(group);
@@ -181,7 +175,7 @@ REGISTER_COMMAND_NAME(@"status")
if ([arguments containsObject:@"--json"]) {
NSMutableDictionary *stats = [@{
@"daemon" : @{
@"driver_connected" : @(driverConnected),
@"driver_connected" : @(YES),
@"mode" : clientMode ?: @"null",
@"file_logging" : @(fileLogging),
@"watchdog_cpu_events" : @(cpuEvents),
@@ -223,20 +217,20 @@ REGISTER_COMMAND_NAME(@"status")
printf("%s\n", [statsStr UTF8String]);
} else {
printf(">>> Daemon Info\n");
printf(" %-25s | %s\n", "Driver Connected", driverConnected ? "Yes" : "No");
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
printf(" %-25s | %s\n", "USB Remounting Mode:",
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
}
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
if (cachingEnabled) {
printf(">>> Cache Info\n");
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
}
printf(">>> Database Info\n");

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

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

@@ -3,7 +3,6 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
)
licenses(["notice"])
@@ -22,11 +21,14 @@ objc_library(
],
deps = [
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
],
)
@@ -52,6 +54,8 @@ objc_library(
deps = [
":SNTEventProvider",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
@@ -69,15 +73,20 @@ objc_library(
"Logs/SNTSyslogEventLog.h",
"SNTDatabaseController.h",
],
hdrs = [
"Logs/SNTEventLog.h",
],
deps = [
":database_controller",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"@FMDB",
],
)
@@ -92,8 +101,14 @@ objc_library(
],
deps = [
":event_logs_common",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:log_objc_proto",
"//Source/common:SNTStoredEvent",
"//Source/common:santa_objc_proto",
],
)
@@ -108,6 +123,12 @@ objc_library(
deps = [
":endpoint_security_manager",
":event_logs_common",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTLogging",
],
)
@@ -116,9 +137,14 @@ objc_library(
srcs = [
"Logs/SNTFileEventLog.h",
"Logs/SNTFileEventLog.m",
"Logs/SNTSyslogEventLog.h",
],
deps = [
":event_logs_common",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTStrengthify",
],
)
@@ -128,7 +154,9 @@ objc_library(
":file_event_logs",
":protobuf_event_logs",
":syslog_event_logs",
"//Source/common:SNTCommon",
],
hdrs = ["Logs/SNTEventLog.h"],
)
objc_library(
@@ -172,21 +200,31 @@ objc_library(
":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:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/santad:SNTApplicationCoreMetrics",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
@@ -210,6 +248,9 @@ objc_library(
"EventProviders/EndpointSecurityTestUtil.h",
"EventProviders/EndpointSecurityTestUtil.mm",
],
hdrs = [
"EventProviders/EndpointSecurityTestUtil.h",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
@@ -248,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"],
@@ -261,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 = [
@@ -273,6 +319,7 @@ santa_unit_test(
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
@@ -280,8 +327,9 @@ santa_unit_test(
"//Source/common:SNTPrefixTree",
"//Source/common:SNTRule",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SantaCache",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
@@ -318,6 +366,7 @@ santa_unit_test(
],
deps = [
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
@@ -326,6 +375,9 @@ santa_unit_test(
"@MOLCertificate",
"@MOLCodesignChecker",
],
sdk_dylibs = [
"EndpointSecurity",
],
)
santa_unit_test(
@@ -343,7 +395,9 @@ santa_unit_test(
],
deps = [
":EndpointSecurityTestLib",
"//Source/common:SNTConfigurator",
"//Source/common:SNTCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
@@ -352,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",
@@ -364,6 +420,8 @@ santa_unit_test(
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTCommon",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
"@OCMock",
@@ -373,7 +431,9 @@ santa_unit_test(
santa_unit_test(
name = "SNTApplicationTest",
srcs = [
"SNTApplication.h",
"SNTApplicationTest.m",
"SNTDatabaseController.h",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
@@ -386,10 +446,14 @@ santa_unit_test(
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTConfigurator",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
tags = ["exclusive"],
)
santa_unit_test(
@@ -423,6 +487,7 @@ santa_unit_test(
deps = [
":SNTApplicationCoreMetrics",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
@@ -433,14 +498,22 @@ santa_unit_test(
santa_unit_test(
name = "SNTProtobufEventLogTest",
srcs = [
"Logs/SNTLogOutput.h",
"Logs/SNTProtobufEventLog.h",
"Logs/SNTProtobufEventLogTest.m",
"Logs/SNTSimpleMaildir.h",
],
minimum_os_version = "10.15",
deps = [
":EndpointSecurityTestLib",
":event_logs",
"//Source/common:SNTAllowlistInfo",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTConfigurator",
"//Source/common:log_objc_proto",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:santa_objc_proto",
"@OCMock",
],
)

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

@@ -21,7 +21,8 @@ es_string_token_t MakeStringToken(const NSString *_Nonnull s);
es_file_t MakeESFile(const char *_Nonnull path);
es_process_t MakeESProcess(es_file_t *_Nonnull esFile);
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *_Nonnull instigator, struct timespec ts);
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *_Nonnull instigator,
struct timespec ts);
CF_EXTERN_C_END
@class ESMessage;
@@ -68,6 +69,21 @@ API_UNAVAILABLE(ios, tvos, watchos)
es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
es_handler_block_t _Nonnull handler);
API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos, watchos)
es_return_t es_mute_process(es_client_t * _Nonnull client,
const audit_token_t * _Nonnull audit_token);
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
API_AVAILABLE(macos(12.0))
API_UNAVAILABLE(ios, tvos, watchos)
es_return_t es_muted_paths_events(es_client_t *_Nonnull client,
es_muted_paths_t *_Nonnull *_Nullable muted_paths);
API_AVAILABLE(macos(12.0))
API_UNAVAILABLE(ios, tvos, watchos)
void es_release_muted_paths(es_muted_paths_t *_Nonnull muted_paths);
#endif
API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos)
es_respond_result_t es_respond_auth_result(es_client_t *_Nonnull client,

View File

@@ -45,7 +45,8 @@ es_process_t MakeESProcess(es_file_t *esFile) {
return esProc;
}
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator, struct timespec ts) {
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator,
struct timespec ts) {
es_message_t esMsg = {};
esMsg.time = ts;
@@ -205,6 +206,18 @@ CF_EXTERN_C_END
[self.clients addObject:mockClient];
}
- (BOOL)removeClient:(es_client_t *_Nonnull)client {
MockESClient *clientToRemove = [self findClient:client];
if (!clientToRemove) {
NSLog(@"Attempted to remove unknown mock es client.");
return NO;
}
[self.clients removeObject:clientToRemove];
return YES;
}
- (void)triggerHandler:(es_message_t *_Nonnull)msg {
for (MockESClient *client in self.clients) {
if (client.subscriptions[msg->event_type]) {
@@ -233,17 +246,24 @@ CF_EXTERN_C_END
return ES_RESPOND_RESULT_SUCCESS;
};
- (MockESClient *)findClient:(es_client_t *)client {
for (MockESClient *c in self.clients) {
// Since we're mocking out a C interface and using this exact pointer as our
// client identifier, only check for pointer equality.
if (client == (__bridge es_client_t *)c) {
return c;
}
}
return nil;
}
- (void)setSubscriptions:(const es_event_type_t *_Nonnull)events
event_count:(uint32_t)event_count
value:(NSNumber *)value
client:(es_client_t *)client {
@synchronized(self) {
MockESClient *toUpdate = nil;
for (MockESClient *c in self.clients) {
if (client == (__bridge es_client_t *)c) {
toUpdate = c;
}
}
MockESClient *toUpdate = [self findClient:client];
if (toUpdate == nil) {
NSLog(@"setting subscription for unknown client");
return;
@@ -281,9 +301,36 @@ es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
return ES_NEW_CLIENT_RESULT_SUCCESS;
};
es_return_t es_mute_process(es_client_t * _Nonnull client,
const audit_token_t * _Nonnull audit_token) {
return ES_RETURN_SUCCESS;
}
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
API_AVAILABLE(macos(12.0))
API_UNAVAILABLE(ios, tvos, watchos)
es_return_t es_muted_paths_events(es_client_t *_Nonnull client,
es_muted_paths_t *_Nonnull *_Nullable muted_paths) {
es_muted_paths_t *tmp = (es_muted_paths_t *)malloc(sizeof(es_muted_paths_t));
tmp->count = 0;
*muted_paths = (es_muted_paths_t *_Nullable)tmp;
return ES_RETURN_SUCCESS;
};
API_AVAILABLE(macos(12.0))
API_UNAVAILABLE(ios, tvos, watchos)
void es_release_muted_paths(es_muted_paths_t *_Nonnull muted_paths) {
free(muted_paths);
}
#endif
API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos) es_return_t es_delete_client(es_client_t *_Nullable client) {
[[MockEndpointSecurity mockEndpointSecurity] reset];
if (![[MockEndpointSecurity mockEndpointSecurity] removeClient:client]) {
return ES_RETURN_ERROR;
}
return ES_RETURN_SUCCESS;
};

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

@@ -170,6 +170,7 @@ NS_ASSUME_NONNULL_BEGIN
es_event_type_t events[] = {
ES_EVENT_TYPE_AUTH_MOUNT,
ES_EVENT_TYPE_AUTH_REMOUNT,
};
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
@@ -199,44 +200,77 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
long mountMode = m->event.mount.statfs->f_flags;
struct statfs *eventStatFS;
BOOL isRemount = NO;
switch (m->event_type) {
case ES_EVENT_TYPE_AUTH_MOUNT: eventStatFS = m->event.mount.statfs; break;
case ES_EVENT_TYPE_AUTH_REMOUNT:
eventStatFS = m->event.remount.statfs;
isRemount = YES;
break;
default:
LOGE(@"Unexpected Event Type passed to DeviceManager handleAuthMount: %d", m->event_type);
// Fail closed.
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
assert(0 && "SNTDeviceManager: unexpected event type");
return;
}
long mountMode = eventStatFS->f_flags;
pid_t pid = audit_token_to_pid(m->process->audit_token);
LOGI(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
LOGD(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
m->process->executable->path.data, pid, mountMode);
DADiskRef disk =
DADiskCreateFromBSDName(NULL, self.diskArbSession, m->event.mount.statfs->f_mntfromname);
DADiskRef disk = DADiskCreateFromBSDName(NULL, self.diskArbSession, eventStatFS->f_mntfromname);
CFAutorelease(disk);
// TODO(tnek): Log all of the other attributes available in diskInfo into a structured log format.
NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk));
BOOL isInternal = [diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceInternalKey] boolValue];
BOOL isRemovable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaRemovableKey] boolValue];
BOOL isUSB =
[diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey] isEqualTo:@"USB"];
BOOL isEjectable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaEjectableKey] boolValue];
NSString *protocol = diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey];
BOOL isUSB = [protocol isEqualToString:@"USB"];
BOOL isVirtual = [protocol isEqualToString: @"Virtual Interface"];
if (!isRemovable || !isUSB) {
NSString *kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey];
// TODO: check kind and protocol for banned things (e.g. MTP).
LOGD(@"SNTDeviceManager: DiskInfo Protocol: %@ Kind: %@ isInternal: %d isRemovable: %d "
@"isEjectable: %d",
protocol, kind, isInternal, isRemovable, isEjectable);
// If the device is internal or virtual we are okay with the operation. We
// also are okay with operations for devices that are non-removal as long as
// they are NOT a USB device.
if (isInternal || isVirtual || (!isRemovable && !isEjectable && !isUSB)) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
SNTDeviceEvent *event = [[SNTDeviceEvent alloc]
initWithOnName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntonname]
fromName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntfromname]];
initWithOnName:[NSString stringWithUTF8String:eventStatFS->f_mntonname]
fromName:[NSString stringWithUTF8String:eventStatFS->f_mntfromname]];
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
if (shouldRemount) {
event.remountArgs = self.remountArgs;
long remountOpts = mountArgsToMask(self.remountArgs);
if (mountMode & remountOpts) {
LOGD(@"SNTDeviceManager: mountMode: %@", maskToMountArgs(mountMode));
LOGD(@"SNTDeviceManager: remountOpts: %@", maskToMountArgs(remountOpts));
if ((mountMode & remountOpts) == remountOpts && !isRemount) {
LOGD(@"SNTDeviceManager: Allowing as mount as flags match remountOpts");
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
long newMode = mountMode | remountOpts;
LOGI(@"SNTDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)",
m->event.mount.statfs->f_mntfromname, m->event.mount.statfs->f_mntonname, mountMode,
newMode);
eventStatFS->f_mntfromname, eventStatFS->f_mntonname, mountMode, newMode);
[self remount:disk mountMode:newMode];
}
@@ -270,26 +304,34 @@ NS_ASSUME_NONNULL_BEGIN
// ES will kill our whole client if we don't meet the es_message auth deadline, so we try to
// gracefully handle it with a deny-by-default in the worst-case before it can do that.
// This isn't an issue for notify events, so we're in no rush for those.
std::shared_ptr<std::atomic<bool>> responded;
es_message_t *mc = es_copy_message(m);
dispatch_semaphore_t processingSema = dispatch_semaphore_create(0);
// Add 1 to the processing semaphore. We're not creating it with a starting
// value of 1 because that requires that the semaphore is not deallocated
// until its value matches the starting value, which we don't need.
dispatch_semaphore_signal(processingSema);
dispatch_semaphore_t deadlineExpiredSema = dispatch_semaphore_create(0);
if (m->action_type == ES_ACTION_TYPE_AUTH) {
responded = std::make_shared<std::atomic<bool>>(false);
dispatch_after(timeout, self.esAuthQueue, ^(void) {
if (responded->load()) return;
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
// Handler already responded, nothing to do.
return;
}
LOGE(@"SNTDeviceManager: deadline reached: deny pid=%d ret=%d",
audit_token_to_pid(m->process->audit_token),
es_respond_auth_result(c, m, ES_AUTH_RESULT_DENY, false));
dispatch_semaphore_signal(deadlineExpiredSema);
});
}
// TODO(tnek): migrate to es_retain_message.
es_message_t *mc = es_copy_message(m);
dispatch_async(self.esAuthQueue, ^{
[self handleESMessage:m withClient:c];
if (m->action_type == ES_ACTION_TYPE_AUTH) {
responded->store(true);
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
// Deadline expired, wait for deadline block to finish.
dispatch_semaphore_wait(deadlineExpiredSema, DISPATCH_TIME_FOREVER);
}
es_free_message(mc);
});
}
@@ -297,16 +339,17 @@ NS_ASSUME_NONNULL_BEGIN
- (void)handleESMessage:(const es_message_t *)m
withClient:(es_client_t *)c API_AVAILABLE(macos(10.15)) {
switch (m->event_type) {
case ES_EVENT_TYPE_AUTH_MOUNT: {
[self handleAuthMount:m withClient:c];
// Intentional fallthrough
case ES_EVENT_TYPE_AUTH_REMOUNT: {
[[fallthrough]];
}
// TODO(tnek): log any extra data here about mounts.
case ES_EVENT_TYPE_NOTIFY_MOUNT: {
case ES_EVENT_TYPE_AUTH_MOUNT: {
[self handleAuthMount:m withClient:c];
break;
}
default: LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
default:
LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
break;
}
}

View File

@@ -40,9 +40,11 @@
fclose(stdout);
}
- (ESResponse *)triggerTestMount:(SNTDeviceManager *)deviceManager
mockES:(MockEndpointSecurity *)mockES
mockDA:(MockDiskArbitration *)mockDA {
- (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager
mockES:(MockEndpointSecurity *)mockES
mockDA:(MockDiskArbitration *)mockDA
eventType:(es_event_type_t)eventType
diskInfoOverrides:(NSDictionary *)diskInfo {
if (!deviceManager.subscribed) {
// [deviceManager listen] is synchronous, but we want to asynchronously dispatch it
// with an enforced timeout to ensure that we never run into issues where the client
@@ -84,26 +86,39 @@
@"DAMediaBSDName" : test_mntfromname,
};
if (diskInfo != nil) {
NSMutableDictionary *mergedDiskDescription = [disk.diskDescription mutableCopy];
for (NSString *key in diskInfo) {
mergedDiskDescription[key] = diskInfo[key];
}
disk.diskDescription = (NSDictionary *)mergedDiskDescription;
}
[mockDA insert:disk bsdName:test_mntfromname];
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
m.binaryPath = @"/System/Library/Filesystems/msdos.fs/Contents/Resources/mount_msdos";
m.message->action_type = ES_ACTION_TYPE_AUTH;
m.message->event_type = ES_EVENT_TYPE_AUTH_MOUNT;
m.message->event = (es_events_t){.mount = {.statfs = fs}};
m.message->event_type = eventType;
if (eventType == ES_EVENT_TYPE_AUTH_MOUNT) {
m.message->event = (es_events_t){.mount = {.statfs = fs}};
} else {
m.message->event = (es_events_t){.remount = {.statfs = fs}};
}
}];
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
XCTestExpectation *mountExpectation =
[self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_MOUNT
[mockES registerResponseCallback:eventType
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
[mountExpectation fulfill];
}];
[mockES triggerHandler:m.message];
[self waitForExpectations:@[ expectation ] timeout:60.0];
[self waitForExpectations:@[ mountExpectation ] timeout:60.0];
free(fs);
return got;
@@ -118,7 +133,12 @@
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = NO;
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:nil];
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
}
@@ -145,7 +165,11 @@
[expectation fulfill];
};
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:nil];
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
XCTAssertEqual(mockDA.wasRemounted, YES);
@@ -179,7 +203,11 @@
[expectation fulfill];
};
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:nil];
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
@@ -190,4 +218,74 @@
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
}
- (void)testEnsureRemountsCannotChangePerms {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = YES;
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
__block NSString *gotmntonname, *gotmntfromname;
__block NSArray<NSString *> *gotRemountedArgs;
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
gotRemountedArgs = event.remountArgs;
gotmntonname = event.mntonname;
gotmntfromname = event.mntfromname;
[expectation fulfill];
};
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_REMOUNT
diskInfoOverrides:nil];
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
XCTAssertEqual(mockDA.wasRemounted, YES);
[self waitForExpectations:@[ expectation ] timeout:10.0];
XCTAssertEqualObjects(gotRemountedArgs, deviceManager.remountArgs);
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
}
- (void)testEnsureDMGsDoNotPrompt {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = YES;
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
XCTFail(@"Should not be called");
};
NSDictionary *diskInfo = @{
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey: @"Virtual Interface",
(__bridge NSString *)kDADiskDescriptionDeviceModelKey: @"Disk Image",
(__bridge NSString *)kDADiskDescriptionMediaNameKey: @"disk image",
};
ESResponse *got = [self triggerTestMountEvent:deviceManager
mockES:mockES
mockDA:mockDA
eventType:ES_EVENT_TYPE_AUTH_MOUNT
diskInfoOverrides:diskInfo];
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
XCTAssertEqual(mockDA.wasRemounted, NO);
}
@end

View File

@@ -34,7 +34,6 @@ static const pid_t PID_MAX = 99999;
@property(nonatomic) SNTPrefixTree *prefixTree;
@property(nonatomic, readonly) dispatch_queue_t esAuthQueue;
@property(nonatomic, readonly) dispatch_queue_t esNotifyQueue;
@property(nonatomic, readonly) pid_t selfPID;
@end
@@ -45,11 +44,10 @@ static const pid_t PID_MAX = 99999;
if (self) {
// To avoid nil deref from es_events arriving before listenForDecisionRequests or
// listenForLogRequests in the MockEndpointSecurity testing util.
_decisionCallback = ^(santa_message_t) {
};
_logCallback = ^(santa_message_t) {
};
_decisionCallback = ^(santa_message_t) {};
_logCallback = ^(santa_message_t) {};
[self establishClient];
[self muteSelf];
_prefixTree = new SNTPrefixTree();
_esAuthQueue =
dispatch_queue_create("com.google.santa.daemon.es_auth", DISPATCH_QUEUE_CONCURRENT);
@@ -58,7 +56,6 @@ static const pid_t PID_MAX = 99999;
_esNotifyQueue =
dispatch_queue_create("com.google.santa.daemon.es_notify", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(_esNotifyQueue, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
_selfPID = getpid();
}
return self;
@@ -72,6 +69,24 @@ static const pid_t PID_MAX = 99999;
if (_prefixTree) delete _prefixTree;
}
- (void)muteSelf {
audit_token_t myAuditToken;
mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT;
if (task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)&myAuditToken, &count) ==
KERN_SUCCESS) {
if (es_mute_process(self.client, &myAuditToken) == ES_RETURN_SUCCESS) {
return;
} else {
LOGE(@"Failed to mute this client's process, its events will not be muted.");
}
} else {
LOGE(@"Failed to fetch this client's audit token. Its events will not be muted.");
}
// If we get here, Santa was unable to mute itself. Assume transitory and bail.
exit(EXIT_FAILURE);
}
- (void)establishClient API_AVAILABLE(macos(10.15)) {
while (!self.client) {
SNTConfigurator *config = [SNTConfigurator configurator];
@@ -86,9 +101,7 @@ static const pid_t PID_MAX = 99999;
if (m->action_type == ES_ACTION_TYPE_AUTH) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
}
if (self.selfPID != pid) {
LOGD(@"Skipping event type: 0x%x from es_client pid: %d", m->event_type, pid);
}
return;
}
@@ -114,9 +127,10 @@ static const pid_t PID_MAX = 99999;
// Create a transitive rule if the file was modified by a running compiler
if ([self isCompilerPID:pid]) {
santa_message_t sm = {};
BOOL truncated = [SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
buffer:sm.path
size:sizeof(sm.path)];
BOOL truncated =
[SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
buffer:sm.path
size:sizeof(sm.path)];
if (truncated) {
LOGE(@"CLOSE: error creating transitive rule, the path is truncated: path=%s pid=%d",
sm.path, pid);
@@ -182,7 +196,12 @@ static const pid_t PID_MAX = 99999;
case ES_EVENT_TYPE_NOTIFY_UNMOUNT: {
// Flush the non-root cache - the root disk cannot be unmounted
// so it isn't necessary to flush its cache.
[self flushCacheNonRootOnly:YES];
//
// Flushing the cache calls back into ES. We need to perform this off the handler thread
// otherwise we could potentially deadlock.
dispatch_async(self.esAuthQueue, ^() {
[self flushCacheNonRootOnly:YES];
});
// Skip all other processing
return;
@@ -210,30 +229,42 @@ static const pid_t PID_MAX = 99999;
switch (m->action_type) {
case ES_ACTION_TYPE_AUTH: {
// Copy the message
es_message_t *mc = es_copy_message(m);
dispatch_semaphore_t processingSema = dispatch_semaphore_create(0);
// Add 1 to the processing semaphore. We're not creating it with a starting
// value of 1 because that requires that the semaphore is not deallocated
// until its value matches the starting value, which we don't need.
dispatch_semaphore_signal(processingSema);
dispatch_semaphore_t deadlineExpiredSema = dispatch_semaphore_create(0);
// Create a timer to deny the execution 5 seconds before the deadline,
// if a response hasn't already been sent. This block will still be enqueued if
// the the deadline - 5 secs is < DISPATCH_TIME_NOW.
// As of 10.15.5, a typical deadline is 60 seconds.
auto responded = std::make_shared<std::atomic<bool>>(false);
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -5), self.esAuthQueue, ^(void) {
if (responded->load()) return;
LOGE(@"Deadline reached: deny pid=%d ret=%d", pid,
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false));
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
// Handler has already responded, nothing to do.
return;
}
LOGE(@"SNTEndpointSecurityManager: deadline reached: deny pid=%d ret=%d", pid,
es_respond_auth_result(self.client, mc, ES_AUTH_RESULT_DENY, false));
dispatch_semaphore_signal(deadlineExpiredSema);
});
// Copy the message and return control back to ES
es_message_t *mc = es_copy_message(m);
// Dispatch off to the handler and return control to ES.
dispatch_async(self.esAuthQueue, ^{
[self messageHandler:mc];
responded->store(true);
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
// Deadline expired, wait for deadline block to finish.
dispatch_semaphore_wait(deadlineExpiredSema, DISPATCH_TIME_FOREVER);
}
es_free_message(mc);
});
break;
}
case ES_ACTION_TYPE_NOTIFY: {
// Don't log fileop events from com.google.santa.daemon
if (self.selfPID == pid && m->event_type != ES_EVENT_TYPE_NOTIFY_EXEC) return;
// Copy the message and return control back to ES
es_message_t *mc = es_copy_message(m);
dispatch_async(self.esNotifyQueue, ^{
@@ -288,6 +319,10 @@ static const pid_t PID_MAX = 99999;
targetFile = m->event.exec.target->executable;
targetProcess = m->event.exec.target;
callback = self.decisionCallback;
[SNTEndpointSecurityManager populateBufferFromESFile:m->process->tty
buffer:sm.ttypath
size:sizeof(sm.ttypath)];
break;
}
case ES_EVENT_TYPE_NOTIFY_EXEC: {
@@ -314,8 +349,7 @@ static const pid_t PID_MAX = 99999;
NSString *path = [[NSString alloc] initWithBytes:pathToken.data
length:pathToken.length
encoding:NSUTF8StringEncoding];
if ([self isDatabasePath:path] &&
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
if ([self isDatabasePath:path]) {
LOGW(@"Preventing attempt to delete Santa databases!");
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
return;
@@ -329,8 +363,7 @@ static const pid_t PID_MAX = 99999;
length:pathToken.length
encoding:NSUTF8StringEncoding];
if ([self isDatabasePath:path] &&
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
if ([self isDatabasePath:path]) {
LOGW(@"Preventing attempt to rename Santa databases!");
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
return;
@@ -340,8 +373,7 @@ static const pid_t PID_MAX = 99999;
NSString *destPath = [[NSString alloc] initWithBytes:destToken.data
length:destToken.length
encoding:NSUTF8StringEncoding];
if ([self isDatabasePath:destPath] &&
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
if ([self isDatabasePath:destPath]) {
LOGW(@"Preventing attempt to overwrite Santa databases!");
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
return;
@@ -544,6 +576,7 @@ static const pid_t PID_MAX = 99999;
// Returns YES if the path was truncated.
// The populated buffer will be NUL terminated.
+ (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
if (file == NULL) return NO;
return [SNTEndpointSecurityManager populateBufferFromString:file->path.data
buffer:buffer
size:size];

View File

@@ -447,7 +447,11 @@
logger = [[SNTProtobufEventLog alloc] init];
break;
}
default: logger = nil;
case SNTEventLogTypeNull: {
// Messages sent to nil objects do nothing, which is perfect for a null logger.
logger = nil;
break;
}
}
});
return logger;

View File

@@ -13,7 +13,6 @@
/// limitations under the License.
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -26,6 +25,7 @@
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCMetricServiceInterface.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
@@ -34,6 +34,7 @@
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
#import "Source/santad/Logs/SNTEventLog.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import "Source/santad/SNTCompilerController.h"
#import "Source/santad/SNTDaemonControlController.h"
#import "Source/santad/SNTDatabaseController.h"
@@ -48,9 +49,9 @@
@property SNTDeviceManager *deviceManager;
@property MOLXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@property MOLXPCConnection *metricsConnection;
@property dispatch_source_t metricsTimer;
@property SNTSyncdQueue *syncdQueue;
@end
@implementation SNTApplication
@@ -114,12 +115,8 @@
[self.eventProvider fileModificationPrefixFilterAdd:[configurator fileChangesPrefixFilters]];
});
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Restart santactl if it goes down
syncdQueue.invalidationHandler = ^{
[self startSyncd];
};
self.notQueue = [[SNTNotificationQueue alloc] init];
self.syncdQueue = [[SNTSyncdQueue alloc] init];
// Listen for actionable config changes.
NSKeyValueObservingOptions bits = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld);
@@ -160,7 +157,7 @@
SNTDaemonControlController *dc =
[[SNTDaemonControlController alloc] initWithEventProvider:_eventProvider
notificationQueue:self.notQueue
syncdQueue:syncdQueue];
syncdQueue:self.syncdQueue];
_controlConnection =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceID]];
@@ -178,9 +175,9 @@
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:self.notQueue
syncdQueue:syncdQueue];
// Start up santactl as a daemon if a sync server exists.
[self startSyncd];
syncdQueue:self.syncdQueue];
// Establish a connection with the sync service if a sync server exists.
[self establishSyncServiceConnection];
if (!_execController) return nil;
@@ -313,27 +310,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
@@ -418,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

@@ -26,7 +26,7 @@
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCBundleServiceInterface.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
@@ -85,10 +85,6 @@ double watchdogRAMPeak = 0;
reply([self.eventProvider checkCache:vnodeID]);
}
- (void)driverConnectionEstablished:(void (^)(BOOL))reply {
reply(self.eventProvider.connectionEstablished);
}
#pragma mark Database ops
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
@@ -252,6 +248,15 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)enableAllEventUpload:(void (^)(BOOL))reply {
reply([SNTConfigurator configurator].enableAllEventUpload);
}
- (void)setEnableAllEventUpload:(BOOL)enabled reply:(void (^)(void))reply {
[[SNTConfigurator configurator] setEnableAllEventUpload:enabled];
reply();
}
#pragma mark Metrics Ops
- (void)metrics:(void (^)(NSDictionary *))reply {
@@ -272,26 +277,8 @@ double watchdogRAMPeak = 0;
#pragma mark syncd Ops
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener {
// Only allow one active syncd connection
if (self.syncdQueue.syncdConnection) return;
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCSyncdInterface syncdInterface];
c.invalidationHandler = ^{
[self.syncdQueue stopSyncingEvents];
[self.syncdQueue.syncdConnection invalidate];
self.syncdQueue.syncdConnection = nil;
if (self.syncdQueue.invalidationHandler) self.syncdQueue.invalidationHandler();
};
c.acceptedHandler = ^{
[self.syncdQueue startSyncingEvents];
};
[c resume];
self.syncdQueue.syncdConnection = c;
}
- (void)pushNotifications:(void (^)(BOOL))reply {
[self.syncdQueue.syncdConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
[self.syncdQueue.syncConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
reply(response);
}];
}

View File

@@ -14,6 +14,7 @@
#import "Source/santad/SNTExecutionController.h"
#include <copyfile.h>
#include <libproc.h>
#include <pwd.h>
#include <utmpx.h>
@@ -135,11 +136,14 @@ static NSString *const kPrinterProxyPostMonterey =
NSError *fileInfoError;
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
if (unlikely(!binInfo)) {
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
if (config.failClosed && config.clientMode == SNTClientModeLockdown) {
LOGE(@"Failed to read file %@: %@ and denying action", @(message.path),
fileInfoError.localizedDescription);
[self.eventProvider postAction:ACTION_RESPOND_DENY forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kDenyNoFileInfo ]];
} else {
LOGE(@"Failed to read file %@: %@ but allowing action", @(message.path),
fileInfoError.localizedDescription);
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kAllowNoFileInfo ]];
}
@@ -175,7 +179,7 @@ static NSString *const kPrinterProxyPostMonterey =
if (action == ACTION_RESPOND_ALLOW) {
[[SNTEventLog logger] cacheDecision:cd];
} else {
ttyPath = [self ttyPathForPID:message.ppid];
ttyPath = @(message.ttypath);
}
// Upgrade the action to ACTION_RESPOND_ALLOW_COMPILER when appropriate, because we want the
@@ -191,9 +195,11 @@ static NSString *const kPrinterProxyPostMonterey =
[self incrementEventCounters:cd.decision];
// Log to database if necessary.
if (cd.decision != SNTEventStateAllowBinary && cd.decision != SNTEventStateAllowCompiler &&
cd.decision != SNTEventStateAllowTransitive && cd.decision != SNTEventStateAllowCertificate &&
cd.decision != SNTEventStateAllowTeamID && cd.decision != SNTEventStateAllowScope) {
if ([SNTConfigurator configurator].enableAllEventUpload ||
(cd.decision != SNTEventStateAllowBinary && cd.decision != SNTEventStateAllowCompiler &&
cd.decision != SNTEventStateAllowTransitive &&
cd.decision != SNTEventStateAllowCertificate && cd.decision != SNTEventStateAllowTeamID &&
cd.decision != SNTEventStateAllowScope)) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.occurrenceDate = [[NSDate alloc] init];
se.fileSHA256 = cd.sha256;
@@ -300,15 +306,12 @@ static NSString *const kPrinterProxyPostMonterey =
SNTFileInfo *proxyFi = [self printerProxyFileInfo];
if ([proxyFi.SHA256 isEqual:fi.SHA256]) return NO;
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyFi.path];
NSFileHandle *outFh = [NSFileHandle fileHandleForWritingAtPath:fi.path];
[outFh writeData:[inFh readDataToEndOfFile]];
[inFh closeFile];
[outFh truncateFileAtOffset:[outFh offsetInFile]];
[outFh synchronizeFile];
[outFh closeFile];
LOGW(@"PrinterProxy workaround applied to %@", fi.path);
copyfile_flags_t copyflags = COPYFILE_ALL | COPYFILE_UNLINK;
if (copyfile(proxyFi.path.UTF8String, fi.path.UTF8String, NULL, copyflags) != 0) {
LOGE(@"Failed to apply PrinterProxy workaround for %@", fi.path);
} else {
LOGI(@"PrinterProxy workaround applied to: %@", fi.path);
}
return YES;
}
@@ -324,19 +327,6 @@ static NSString *const kPrinterProxyPostMonterey =
return proxyInfo;
}
- (NSString *)ttyPathForPID:(pid_t)pid {
if (pid < 2) return nil; // don't bother even looking for launchd.
struct proc_bsdinfo taskInfo = {};
if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &taskInfo, sizeof(taskInfo)) < 1) return nil;
// 16-bytes here is for future-proofing. Currently kern.tty.ptmx_max is
// limited to 999 so 12 bytes should be enough.
char devPath[16] = "/dev/";
snprintf(devPath, 16, "/dev/%s", devname(taskInfo.e_tdev, S_IFCHR));
return @(devPath);
}
- (void)printMessage:(NSString *)msg toTTY:(NSString *)path {
int fd = open(path.UTF8String, O_WRONLY | O_NOCTTY);
write(fd, msg.UTF8String, msg.length);

View File

@@ -21,13 +21,9 @@
@interface SNTSyncdQueue : NSObject
@property(nonatomic) MOLXPCConnection *syncdConnection;
@property(copy) void (^invalidationHandler)(void);
@property(copy) void (^acceptedHandler)(void);
@property(nonatomic) MOLXPCConnection *syncConnection;
- (void)addEvents:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply;
- (void)startSyncingEvents;
- (void)stopSyncingEvents;
@end

View File

@@ -18,12 +18,11 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
@interface SNTSyncdQueue ()
@property NSCache<NSString *, NSDate *> *uploadBackoff;
@property dispatch_queue_t syncdQueue;
@property dispatch_semaphore_t sema;
@end
@implementation SNTSyncdQueue
@@ -34,7 +33,6 @@
_uploadBackoff = [[NSCache alloc] init];
_uploadBackoff.countLimit = 128;
_syncdQueue = dispatch_queue_create("com.google.syncd_queue", DISPATCH_QUEUE_SERIAL);
_sema = dispatch_semaphore_create(0);
}
return self;
}
@@ -45,15 +43,14 @@
NSString *hash = isFromBundle ? first.fileBundleHash : first.fileSHA256;
if (![self backoffForPrimaryHash:hash]) return;
[self dispatchBlockOnSyncdQueue:^{
[self.syncdConnection.remoteObjectProxy postEventsToSyncServer:events
isFromBundle:isFromBundle];
[self.syncConnection.remoteObjectProxy postEventsToSyncServer:events fromBundle:isFromBundle];
}];
}
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply {
if (![self backoffForPrimaryHash:event.fileBundleHash]) return;
[self dispatchBlockOnSyncdQueue:^{
[self.syncdConnection.remoteObjectProxy
[self.syncConnection.remoteObjectProxy
postBundleEventToSyncServer:event
reply:^(SNTBundleEventAction action) {
// Remove the backoff entry for the initial block event. The same
@@ -67,25 +64,10 @@
}];
}
- (void)startSyncingEvents {
dispatch_semaphore_signal(self.sema);
}
- (void)stopSyncingEvents {
self.sema = dispatch_semaphore_create(0);
}
// Hold events for a few seconds to allow santad and santactl to establish connections.
// If the connections are not established in time drop the event from the queue.
// They will be uploaded during a full sync.
- (void)dispatchBlockOnSyncdQueue:(void (^)(void))block {
if (!block) return;
dispatch_async(self.syncdQueue, ^{
if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
if (block) block();
dispatch_semaphore_signal(self.sema);
} else {
LOGD(@"Dropping block %@ from com.google.syncd_queue", block);
}
block();
});
}

View File

@@ -3,7 +3,6 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
)
licenses(["notice"])
@@ -15,6 +14,9 @@ objc_library(
"SNTMetricService.m",
"main.m",
],
hdrs = [
"SNTMetricService.h",
],
deps = [
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
@@ -36,8 +38,12 @@ santa_unit_test(
structured_resources = ["//Source/santametricservice/Formats:testdata"],
deps = [
":SNTMetricServiceLib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
"@OCMock",
"@MOLAuthenticatingURLSession",
],
)
@@ -62,7 +68,7 @@ macos_command_line_application(
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

@@ -2,7 +2,6 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
)
licenses(["notice"])
@@ -30,6 +29,9 @@ objc_library(
"SNTMetricRawJSONFormat.h",
"SNTMetricRawJSONFormat.m",
],
hdrs = [
"SNTMetricRawJSONFormat.h",
],
deps = [
":SNTMetricFormat",
"//Source/common:SNTLogging",
@@ -43,6 +45,9 @@ objc_library(
"SNTMetricMonarchJSONFormat.h",
"SNTMetricMonarchJSONFormat.m",
],
hdrs = [
"SNTMetricMonarchJSONFormat.h",
],
deps = [
":SNTMetricFormat",
"//Source/common:SNTLogging",

View File

@@ -2,7 +2,6 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
)
licenses(["notice"])
@@ -18,6 +17,9 @@ objc_library(
"SNTMetricFileWriter.h",
"SNTMetricFileWriter.m",
],
hdrs = [
"SNTMetricFileWriter.h",
],
deps = [
":SNTMetricWriter",
"//Source/common:SNTLogging",
@@ -31,6 +33,7 @@ santa_unit_test(
],
deps = [
":SNTMetricFileWriter",
"//Source/common:SNTConfigurator",
],
)
@@ -40,6 +43,9 @@ objc_library(
"SNTMetricHTTPWriter.h",
"SNTMetricHTTPWriter.m",
],
hdrs = [
"SNTMetricHTTPWriter.h",
],
deps = [
":SNTMetricWriter",
"//Source/common:SNTConfigurator",
@@ -55,7 +61,9 @@ santa_unit_test(
],
deps = [
":SNTMetricHTTPWriter",
"//Source/common:SNTConfigurator",
"@OCMock",
"@MOLAuthenticatingURLSession",
],
)

View File

@@ -5,7 +5,6 @@ licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
)
objc_library(
@@ -23,10 +22,16 @@ objc_library(
srcs = [
"NSData+Zlib.h",
"NSData+Zlib.m",
"SNTPushNotifications.h",
"SNTPushNotifications.m",
"SNTPushNotificationsTracker.h",
"SNTPushNotificationsTracker.m",
"SNTSyncConstants.h",
"SNTSyncConstants.m",
"SNTSyncEventUpload.h",
"SNTSyncEventUpload.m",
"SNTSyncLogging.h",
"SNTSyncLogging.m",
"SNTSyncManager.m",
"SNTSyncPostflight.h",
"SNTSyncPostflight.m",
@@ -43,11 +48,20 @@ objc_library(
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
":broadcaster_lib",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"@MOLAuthenticatingURLSession",
"@MOLCertificate",
"@MOLXPCConnection",
],
)
@@ -60,10 +74,16 @@ santa_unit_test(
srcs = [
"NSData+Zlib.h",
"NSData+Zlib.m",
"SNTPushNotifications.h",
"SNTPushNotifications.m",
"SNTPushNotificationsTracker.h",
"SNTPushNotificationsTracker.m",
"SNTSyncConstants.h",
"SNTSyncConstants.m",
"SNTSyncEventUpload.h",
"SNTSyncEventUpload.m",
"SNTSyncLogging.h",
"SNTSyncLogging.m",
"SNTSyncPostflight.h",
"SNTSyncPostflight.m",
"SNTSyncPreflight.h",
@@ -83,6 +103,8 @@ santa_unit_test(
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
":broadcaster_lib",
"//Source/common:SNTCommon",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
@@ -90,14 +112,26 @@ santa_unit_test(
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLCertificate",
"@MOLXPCConnection",
"@OCMock",
],
)
objc_library(
name = "broadcaster_lib",
srcs = ["SNTSyncBroadcaster.m"],
hdrs = ["SNTSyncBroadcaster.h"],
deps = [
"//Source/common:SNTXPCSyncServiceInterface",
"@MOLXPCConnection",
],
)
objc_library(
name = "santass_lib",
srcs = [
@@ -106,7 +140,11 @@ objc_library(
"main.m",
],
deps = [
":broadcaster_lib",
":sync_lib",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTLogging",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"@MOLCodesignChecker",
"@MOLXPCConnection",
@@ -116,8 +154,17 @@ objc_library(
macos_command_line_application(
name = "santasyncservice",
bundle_id = "com.google.santa.syncservice",
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 = [":santass_lib"],

View File

@@ -0,0 +1,37 @@
/// 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>
@protocol SNTPushNotificationsDelegate
- (void)sync;
- (void)syncSecondsFromNow:(uint64_t)seconds;
- (void)ruleSync;
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds;
- (void)preflightSync;
@end
@class SNTSyncState;
@class SNTSyncFCM;
@interface SNTPushNotifications : NSObject
- (void)listenWithSyncState:(SNTSyncState *)syncState;
- (void)stop;
@property(weak) id<SNTPushNotificationsDelegate> delegate;
@property(readonly) BOOL isConnected;
@property(readonly) NSString *token;
@property(readonly) NSUInteger pushNotificationsFullSyncInterval;
@end

View File

@@ -0,0 +1,189 @@
/// 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 "Source/santasyncservice/SNTPushNotifications.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncFCM.h"
#import "Source/santasyncservice/SNTSyncState.h"
static NSString *const kFCMActionKey = @"action";
static NSString *const kFCMFileHashKey = @"file_hash";
static NSString *const kFCMFileNameKey = @"file_name";
static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@interface SNTPushNotifications ()
@property SNTSyncFCM *FCMClient;
@property NSString *token;
@property NSUInteger pushNotificationsFullSyncInterval;
@property NSUInteger pushNotificationsGlobalRuleSyncDeadline;
@end
@implementation SNTPushNotifications
#pragma mark push notification methods
- (instancetype)init {
self = [super init];
if (self) {
_pushNotificationsFullSyncInterval = kDefaultPushNotificationsFullSyncInterval;
_pushNotificationsGlobalRuleSyncDeadline = kDefaultPushNotificationsGlobalRuleSyncDeadline;
}
return self;
}
- (void)listenWithSyncState:(SNTSyncState *)syncState {
self.pushNotificationsFullSyncInterval = syncState.pushNotificationsFullSyncInterval;
self.pushNotificationsGlobalRuleSyncDeadline = syncState.pushNotificationsGlobalRuleSyncDeadline;
if ([self.token isEqualToString:syncState.pushNotificationsToken]) {
LOGD(@"Already listening for push notifications");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTSyncFCM alloc] initWithProject:config.fcmProject
entity:config.fcmEntity
apiKey:config.fcmAPIKey
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || message[@"noOp"]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.tokenHandler = ^(NSString *t) {
STRONGIFY(self);
LOGD(@"tokenHandler: %@", t);
self.token = t;
[self.delegate preflightSync];
};
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.token = nil;
[self.delegate syncSecondsFromNow:kDefaultFullSyncInterval];
};
[self.FCMClient connect];
}
- (void)stop {
[self.FCMClient disconnect];
self.FCMClient = nil;
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSDictionary *message = [self messageFromMessageData:[self messageDataFromFCMmessage:FCMmessage]];
if (!message) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSString *action = message[kFCMActionKey];
if (!action) {
LOGD(@"Push notification message contains no action");
return;
}
// We assume that the incoming FCM message contains name of binary/bundle and a hash. Rule count
// info for bundles will be sent out later with the rules themselves. If the message is related
// to a bundle, the hash is a bundle hash, otherwise it is just a hash for a single binary.
// For later use, we store a mapping of bundle/binary hash to a dictionary containing the
// binary/bundle name so we can send out relevant notifications once the rules are actually
// downloaded & added to local database. We use a dictionary value so that we can later add a
// count field when we start downloading the rules and receive the count information.
NSString *fileHash = message[kFCMFileHashKey];
NSString *fileName = message[kFCMFileNameKey];
if (fileName && fileHash) {
[[SNTPushNotificationsTracker tracker] addNotification:[@{kFileName : fileName} mutableCopy]
forHash:fileHash];
}
LOGD(@"Push notification action '%@' received", action);
if ([action isEqualToString:kFullSync] || [action isEqualToString:kConfigSync] ||
[action isEqualToString:kLogSync]) {
[self.delegate sync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetHostID = message[kFCMTargetHostIDKey];
if (targetHostID && [targetHostID caseInsensitiveCompare:machineID] == NSOrderedSame) {
LOGD(@"Targeted rule_sync for host_id: %@", targetHostID);
[self.delegate ruleSync];
} else {
uint32_t delaySeconds =
arc4random_uniform((uint32_t)self.pushNotificationsGlobalRuleSyncDeadline);
LOGD(@"Global rule_sync, staggering: %u second delay", delaySeconds);
[self.delegate ruleSyncSecondsFromNow:delaySeconds];
}
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
- (NSData *)messageDataFromFCMmessage:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
if (!messageData) {
LOGD(@"Unable to parse push notification message data");
return nil;
}
NSError *error;
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!rawMessage) {
LOGD(@"Unable to parse push notification message data: %@", error);
return nil;
}
// Create a new message dropping unexpected values
NSArray *allowedKeys = @[ kFCMActionKey, kFCMFileHashKey, kFCMFileNameKey, kFCMTargetHostIDKey ];
NSMutableDictionary *message = [NSMutableDictionary dictionaryWithCapacity:allowedKeys.count];
for (NSString *key in allowedKeys) {
if ([rawMessage[key] isKindOfClass:[NSString class]] && [rawMessage[key] length]) {
message[key] = rawMessage[key];
}
}
return message.count ? [message copy] : nil;
}
- (BOOL)isConnected {
return self.FCMClient.isConnected;
}
@end

View File

@@ -0,0 +1,31 @@
/// 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>
// SNTPushNotificationsTracker stores info from push notification messages. The binary/bundle hash
// is used as a key mapping to values that are themselves dictionaries. These dictionary values
// contain the name of the binary/bundle and a count of associated binary rules.
@interface SNTPushNotificationsTracker : NSObject
// Retrieve an initialized singleton SNTPushNotificationsTracker object.
// Use this instead of init.
+ (instancetype)tracker;
- (void)addNotification:(NSDictionary *)notification forHash:(NSString *)hash;
- (void)removeNotificationsForHashes:(NSArray<NSString *> *)hashes;
- (void)decrementPendingRulesForHash:(NSString *)hash totalRuleCount:(NSNumber *)totalRuleCount;
- (NSDictionary *)all;
@end

View File

@@ -0,0 +1,99 @@
/// 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 "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import "Source/common/SNTLogging.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
@interface SNTPushNotificationsTracker ()
@property dispatch_queue_t notificationsQueue;
@property NSMutableDictionary *notifications;
@end
@implementation SNTPushNotificationsTracker
- (instancetype)init {
self = [super init];
if (self) {
_notifications = [NSMutableDictionary dictionary];
_notificationsQueue =
dispatch_queue_create("com.google.santa.syncservice.notifications", DISPATCH_QUEUE_SERIAL);
}
return self;
}
+ (instancetype)tracker {
static SNTPushNotificationsTracker *tracker;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
tracker = [[SNTPushNotificationsTracker alloc] init];
});
return tracker;
}
- (void)addNotification:(NSDictionary *)notification forHash:(NSString *)hash {
dispatch_async(self.notificationsQueue, ^() {
// Don't let notifications pile up. In most cases there will only be a single entry pending. It
// is possible for notifications to make it here but not be displayed. The TODO below is to
// address this.
// TODO(bur): Add better guaranties for displaying notifications. This will involve checking the
// rules.db to see if the rule associated with the notification has been applied.
if (self.notifications.count > 16) {
LOGE(@"Push notifications are not being processed. Dropping pending notifications.");
[self.notifications removeAllObjects];
}
self.notifications[hash] = notification;
});
}
- (void)removeNotificationsForHashes:(NSArray<NSString *> *)hashes {
dispatch_async(self.notificationsQueue, ^() {
[self.notifications removeObjectsForKeys:hashes];
});
}
- (void)decrementPendingRulesForHash:(NSString *)hash totalRuleCount:(NSNumber *)totalRuleCount {
dispatch_async(self.notificationsQueue, ^() {
NSMutableDictionary *notifier = self.notifications[hash];
if (notifier) {
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining) { // bundle rule with existing count
// If the primary hash already has an associated count field, just decrement it.
notifier[kFileBundleBinaryCount] = @([remaining intValue] - 1);
} else if (totalRuleCount) { // bundle rule seen for first time
// Downloaded rules including count information are associated with bundles.
// The first time we see a rule for a given bundle hash, add a count field with an
// initial value equal to the number of associated rules, then decrement this value by 1
// to account for the rule that we've just downloaded.
notifier[kFileBundleBinaryCount] = @([totalRuleCount intValue] - 1);
} else { // non-bundle binary rule
// Downloaded rule had no count information, meaning it is a singleton non-bundle rule.
// Therefore there are no more rules associated with this hash to download.
notifier[kFileBundleBinaryCount] = @0;
}
}
});
}
- (NSDictionary *)all {
__block NSDictionary *d;
dispatch_sync(self.notificationsQueue, ^() {
d = self.notifications;
});
return d;
}
@end

View File

@@ -0,0 +1,33 @@
/// 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>
@class MOLXPCConnection;
// A small class to keep track of and send messages to active listeners.
@interface SNTSyncBroadcaster : NSObject
// Retrieve an initialized singleton SNTSyncBroadcaster object.
// Use this instead of init.
+ (instancetype)broadcaster;
- (void)addLogListener:(MOLXPCConnection *)logListener;
- (void)removeLogListener:(MOLXPCConnection *)logListener;
- (void)broadcastToLogListeners:(NSString *)log;
// Blocks until all the currently enqueued (up to this point) logs from -[broadcastToLogListeners:]
// are sent.
- (void)barrier;
@end

View File

@@ -0,0 +1,73 @@
/// 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 "Source/santasyncservice/SNTSyncBroadcaster.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTXPCSyncServiceInterface.h"
@interface SNTSyncBroadcaster ()
@property NSMutableArray *logListeners;
@property dispatch_queue_t broadcastQueue;
@end
@implementation SNTSyncBroadcaster
- (instancetype)init {
self = [super init];
if (self) {
_logListeners = [NSMutableArray array];
_broadcastQueue =
dispatch_queue_create("com.google.santa.syncservice.broadcast", DISPATCH_QUEUE_SERIAL);
}
return self;
}
+ (instancetype)broadcaster {
static SNTSyncBroadcaster *sharedBroadcaster;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedBroadcaster = [[SNTSyncBroadcaster alloc] init];
});
return sharedBroadcaster;
}
- (void)addLogListener:(MOLXPCConnection *)logListener {
dispatch_async(self.broadcastQueue, ^() {
[self.logListeners addObject:logListener];
});
}
- (void)removeLogListener:(MOLXPCConnection *)logListener {
dispatch_async(self.broadcastQueue, ^() {
[self.logListeners removeObject:logListener];
});
}
- (void)broadcastToLogListeners:(NSString *)log {
dispatch_async(self.broadcastQueue, ^() {
for (MOLXPCConnection *ll in self.logListeners) {
[[ll remoteObjectProxy] didReceiveLog:log];
}
});
}
- (void)barrier {
dispatch_sync(self.broadcastQueue, ^() {
return;
});
}
@end

View File

@@ -50,6 +50,7 @@ extern NSString *const kEnableBundlesDeprecated;
extern NSString *const kEnableTransitiveRules;
extern NSString *const kEnableTransitiveRulesDeprecated;
extern NSString *const kEnableTransitiveRulesSuperDeprecated;
extern NSString *const kEnableAllEventUpload;
extern NSString *const kEvents;
extern NSString *const kFileSHA256;
@@ -62,10 +63,12 @@ extern NSString *const kDecisionAllowUnknown;
extern NSString *const kDecisionAllowBinary;
extern NSString *const kDecisionAllowCertificate;
extern NSString *const kDecisionAllowScope;
extern NSString *const kDecisionAllowTeamID;
extern NSString *const kDecisionBlockUnknown;
extern NSString *const kDecisionBlockBinary;
extern NSString *const kDecisionBlockCertificate;
extern NSString *const kDecisionBlockScope;
extern NSString *const kDecisionBlockTeamID;
extern NSString *const kDecisionUnknown;
extern NSString *const kDecisionBundleBinary;
extern NSString *const kLoggedInUsers;
@@ -132,5 +135,5 @@ extern const NSUInteger kDefaultEventBatchSize;
/// Are represented in seconds
///
extern const NSUInteger kDefaultFullSyncInterval;
extern const NSUInteger kDefaultFCMFullSyncInterval;
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;
extern const NSUInteger kDefaultPushNotificationsFullSyncInterval;
extern const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline;

View File

@@ -51,6 +51,7 @@ NSString *const kEnableBundlesDeprecated = @"bundles_enabled";
NSString *const kEnableTransitiveRules = @"enable_transitive_rules";
NSString *const kEnableTransitiveRulesDeprecated = @"enabled_transitive_whitelisting";
NSString *const kEnableTransitiveRulesSuperDeprecated = @"transitive_whitelisting_enabled";
NSString *const kEnableAllEventUpload = @"enable_all_event_upload";
NSString *const kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -63,10 +64,12 @@ NSString *const kDecisionAllowUnknown = @"ALLOW_UNKNOWN";
NSString *const kDecisionAllowBinary = @"ALLOW_BINARY";
NSString *const kDecisionAllowCertificate = @"ALLOW_CERTIFICATE";
NSString *const kDecisionAllowScope = @"ALLOW_SCOPE";
NSString *const kDecisionAllowTeamID = @"ALLOW_TEAMID";
NSString *const kDecisionBlockUnknown = @"BLOCK_UNKNOWN";
NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
NSString *const kDecisionBlockTeamID = @"BLOCK_TEAMID";
NSString *const kDecisionUnknown = @"UNKNOWN";
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
NSString *const kLoggedInUsers = @"logged_in_users";
@@ -125,5 +128,5 @@ NSString *const kLogSync = @"log_sync";
const NSUInteger kDefaultEventBatchSize = 50;
const NSUInteger kDefaultFullSyncInterval = 600;
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;
const NSUInteger kDefaultPushNotificationsFullSyncInterval = 14400;
const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline = 600;

View File

@@ -17,12 +17,14 @@
#import <MOLCertificate/MOLCertificate.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTFileInfo.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/NSData+Zlib.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTSyncEventUpload
@@ -53,14 +55,14 @@
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
}
if (!self.syncState.cleanSync) {
if (!self.syncState.cleanSync || [[SNTConfigurator configurator] enableCleanSyncEventUpload]) {
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{kEvents : uploadEvents}]];
if (!r) return NO;
// A list of bundle hashes that require their related binary events to be uploaded.
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
LOGI(@"Uploaded %lu events", uploadEvents.count);
SLOGI(@"Uploaded %lu events", uploadEvents.count);
}
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
@@ -98,12 +100,14 @@
ADDKEY(newEvent, kDecision, kDecisionAllowCertificate);
break;
case SNTEventStateAllowScope: ADDKEY(newEvent, kDecision, kDecisionAllowScope); break;
case SNTEventStateAllowTeamID: ADDKEY(newEvent, kDecision, kDecisionAllowTeamID); break;
case SNTEventStateBlockUnknown: ADDKEY(newEvent, kDecision, kDecisionBlockUnknown); break;
case SNTEventStateBlockBinary: ADDKEY(newEvent, kDecision, kDecisionBlockBinary); break;
case SNTEventStateBlockCertificate:
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
break;
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case SNTEventStateBlockTeamID: ADDKEY(newEvent, kDecision, kDecisionBlockTeamID); break;
case SNTEventStateBundleBinary:
ADDKEY(newEvent, kDecision, kDecisionBundleBinary);
[newEvent removeObjectForKey:kExecutionTime];

View File

@@ -0,0 +1,49 @@
/// 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 "Source/common/SNTLogging.h"
void logSyncMessage(LogLevel level, NSString *format, ...)
__attribute__((format(__NSString__, 2, 3)));
///
/// Send logs to the standard pipeline AND to any active sync listeners.
/// Intended for use by the syncservice to send logs back to santactl instances.
/// LOG*() and logSyncMessage() both end up using a va_list which is single use. We are calling
/// both routines in this macro so they each get a copy of __VA_ARGS__.
///
/// TODO(bur): SLOGD() is temporarily set to LOG_LEVEL_INFO. Once santactl sync supports the
/// --debug flag, move this back to LOG_LEVEL_DEBUG. These debug logs are helpful when
/// troubleshooting sync issues with users, so let's opt to always log them for now.
///
#define SLOGD(logFormat, ...) \
do { \
LOGD(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_INFO, logFormat, ##__VA_ARGS__); \
} while (0)
#define SLOGI(logFormat, ...) \
do { \
LOGI(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_INFO, logFormat, ##__VA_ARGS__); \
} while (0)
#define SLOGW(logFormat, ...) \
do { \
LOGW(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_WARN, logFormat, ##__VA_ARGS__); \
} while (0)
#define SLOGE(logFormat, ...) \
do { \
LOGE(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_ERROR, logFormat, ##__VA_ARGS__); \
} while (0)

View File

@@ -0,0 +1,32 @@
/// 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 "Source/santasyncservice/SNTSyncLogging.h"
#include "Source/common/SNTLogging.h"
#import "Source/santasyncservice/SNTSyncBroadcaster.h"
void logSyncMessage(LogLevel level, NSString *format, ...) {
static LogLevel logLevel = LOG_LEVEL_DEBUG;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
logLevel = EffectiveLogLevel();
});
if (logLevel < level) return;
va_list args;
va_start(args, format);
NSMutableString *s = [[NSMutableString alloc] initWithFormat:format arguments:args];
va_end(args);
[[SNTSyncBroadcaster broadcaster] broadcastToLogListeners:s];
}

View File

@@ -14,16 +14,14 @@
#import <Foundation/Foundation.h>
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
@class MOLXPCConnection;
///
/// Handles push notifications and periodic syncing with a sync server.
///
@interface SNTSyncManager : NSObject <SNTSyncdXPC>
@property(readonly, nonatomic) BOOL daemon;
@interface SNTSyncManager : NSObject
///
/// Use the designated initializer initWithDaemonConnection:isDaemon:
@@ -34,22 +32,42 @@
/// Designated initializer.
///
/// @param daemonConn A connection to santad.
/// @param daemon Set to YES if periodic syncing should occur.
/// Set to NO if a single sync should be performed. NO is default.
///
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn
isDaemon:(BOOL)daemon NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn NS_DESIGNATED_INITIALIZER;
///
/// Perform a full sync immediately. Non-blocking.
/// If a full sync is already running new requests will be dropped.
/// Perform a sync immediately. Non-blocking.
/// If a sync is already running new requests will be dropped.
///
- (void)fullSync;
- (void)sync;
///
/// Perform a full sync seconds from now. Non-blocking.
/// If a full sync is already running new requests will be dropped.
/// Perform a sync seconds from now. Non-blocking.
/// If a sync is already running new requests will be dropped.
///
- (void)fullSyncSecondsFromNow:(uint64_t)seconds;
- (void)syncSecondsFromNow:(uint64_t)seconds;
///
/// Perform an out of band sync.
///
/// 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.
///
/// The SNTSyncStatusTypeSyncStarted will be passed into the reply block when the sync starts. The
/// reply block will be called again with a SNTSyncStatusType when the sync has completed or
/// failed.
///
/// Pass true to isClean to perform a clean sync, defaults to false.
///
- (void)syncAndMakeItClean:(BOOL)clean withReply:(void (^)(SNTSyncStatusType))reply;
///
/// Handle SNTSyncServiceXPC messages forwarded from SNTSyncService.
///
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)isFromBundle;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end

View File

@@ -24,52 +24,35 @@
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/santasyncservice/SNTPushNotifications.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncEventUpload.h"
#import "Source/santasyncservice/SNTSyncFCM.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncPostflight.h"
#import "Source/santasyncservice/SNTSyncPreflight.h"
#import "Source/santasyncservice/SNTSyncRuleDownload.h"
#import "Source/santasyncservice/SNTSyncState.h"
static NSString *const kFCMActionKey = @"action";
static NSString *const kFCMFileHashKey = @"file_hash";
static NSString *const kFCMFileNameKey = @"file_name";
static NSString *const kFCMTargetHostIDKey = @"target_host_id";
static const uint8_t kMaxEnqueuedSyncs = 2;
@interface SNTSyncManager () {
@interface SNTSyncManager () <SNTPushNotificationsDelegate> {
SCNetworkReachabilityRef _reachability;
}
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@property(nonatomic) NSCache *dispatchLock;
// allowlistNotifications dictionary stores info from FCM messages. The binary/bundle hash is used
// as a key mapping to values that are themselves dictionaries. These dictionary values contain the
// name of the binary/bundle and a count of associated binary rules.
@property(nonatomic) NSMutableDictionary *allowlistNotifications;
// allowlistNotificationQueue is used to serialize access to the allowlistNotifications dictionary.
@property(nonatomic) NSOperationQueue *allowlistNotificationQueue;
@property NSUInteger fullSyncInterval;
@property NSUInteger FCMFullSyncInterval;
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property SNTSyncFCM *FCMClient;
@property NSString *FCMToken;
@property(nonatomic, readonly) dispatch_queue_t syncQueue;
@property(nonatomic, readonly) dispatch_semaphore_t syncLimiter;
@property(nonatomic) MOLXPCConnection *daemonConn;
@property BOOL targetedRuleSync;
@property(nonatomic) BOOL reachable;
@property SNTPushNotifications *pushNotifications;
@property NSUInteger eventBatchSize;
@end
// Called when the network state changes
@@ -90,45 +73,26 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
#pragma mark init
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn isDaemon:(BOOL)daemon {
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn {
self = [super init];
if (self) {
_daemonConn = daemonConn;
_daemon = daemon;
_pushNotifications = [[SNTPushNotifications alloc] init];
_pushNotifications.delegate = self;
_fullSyncTimer = [self createSyncTimerWithBlock:^{
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.FCMFullSyncInterval];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kFullSync];
[self preflight];
[self unlockAction:kFullSync];
[self rescheduleTimerQueue:self.fullSyncTimer
secondsFromNow:_pushNotifications.pushNotificationsFullSyncInterval];
[self syncAndMakeItClean:NO withReply:NULL];
}];
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
dispatch_source_set_timer(self.ruleSyncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER,
0);
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kRuleSync];
SNTSyncState *syncState = [self createSyncState];
syncState.targetedRuleSync = self.targetedRuleSync;
syncState.allowlistNotifications = self.allowlistNotifications;
syncState.allowlistNotificationQueue = self.allowlistNotificationQueue;
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
} else {
LOGE(@"Rule download failed");
}
self.targetedRuleSync = NO;
[self unlockAction:kRuleSync];
[self ruleSyncImpl];
}];
_dispatchLock = [[NSCache alloc] init];
_allowlistNotifications = [NSMutableDictionary dictionary];
_allowlistNotificationQueue = [[NSOperationQueue alloc] init];
_allowlistNotificationQueue.maxConcurrentOperationCount = 1; // make this a serial queue
_syncQueue = dispatch_queue_create("com.google.santa.syncservice", DISPATCH_QUEUE_SERIAL);
_syncLimiter = dispatch_semaphore_create(kMaxEnqueuedSyncs);
_fullSyncInterval = kDefaultFullSyncInterval;
_eventBatchSize = kDefaultEventBatchSize;
_FCMFullSyncInterval = kDefaultFCMFullSyncInterval;
_FCMGlobalRuleSyncDeadline = kDefaultFCMGlobalRuleSyncDeadline;
}
return self;
}
@@ -138,10 +102,15 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
[self stopReachability];
}
#pragma mark SNTSyncdXPC protocol methods
#pragma mark SNTSyncServiceXPC methods
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle {
SNTSyncState *syncState = [self createSyncState];
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)isFromBundle {
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
LOGE(@"Events upload failed to create sync state: %ld", status);
return;
}
if (isFromBundle) syncState.eventBatchSize = self.eventBatchSize;
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if (events && [p uploadEvents:events]) {
@@ -159,7 +128,13 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
reply(SNTBundleEventActionDropEvents);
return;
}
SNTSyncState *syncState = [self createSyncState];
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
LOGE(@"Bundle event upload failed to create sync state: %ld", status);
reply(SNTBundleEventActionDropEvents);
return;
}
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if ([p uploadEvents:@[ event ]]) {
if ([syncState.bundleBinaryRequests containsObject:event.fileBundleHash]) {
@@ -180,162 +155,50 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply(self.FCMClient.isConnected);
reply(self.pushNotifications.isConnected);
}
#pragma mark push notification methods
#pragma mark sync control / SNTPushNotificationsDelegate methods
- (void)listenForPushNotificationsWithSyncState:(SNTSyncState *)syncState {
if ([self.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Already listening for push notifications");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTSyncFCM alloc] initWithProject:config.fcmProject
entity:config.fcmEntity
apiKey:config.fcmAPIKey
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || message[@"noOp"]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.tokenHandler = ^(NSString *t) {
STRONGIFY(self);
LOGD(@"tokenHandler: %@", t);
self.FCMToken = t;
[self preflightOnly:YES];
};
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.FCMToken = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
[self.FCMClient connect];
- (void)sync {
[self syncSecondsFromNow:0];
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSDictionary *message = [self messageFromMessageData:[self messageDataFromFCMmessage:FCMmessage]];
if (!message) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSString *action = message[kFCMActionKey];
if (!action) {
LOGD(@"Push notification message contains no action");
return;
}
// We assume that the incoming FCM message contains name of binary/bundle and a hash. Rule count
// info for bundles will be sent out later with the rules themselves. If the message is related
// to a bundle, the hash is a bundle hash, otherwise it is just a hash for a single binary.
// For later use, we store a mapping of bundle/binary hash to a dictionary containing the
// binary/bundle name so we can send out relevant notifications once the rules are actually
// downloaded & added to local database. We use a dictionary value so that we can later add a
// count field when we start downloading the rules and receive the count information.
NSString *fileHash = message[kFCMFileHashKey];
NSString *fileName = message[kFCMFileNameKey];
if (fileName && fileHash) {
[self.allowlistNotificationQueue addOperationWithBlock:^{
self.allowlistNotifications[fileHash] = @{kFileName : fileName}.mutableCopy;
}];
}
LOGD(@"Push notification action: %@ received", action);
if ([action isEqualToString:kFullSync]) {
[self fullSync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetHostID = message[kFCMTargetHostIDKey];
if (targetHostID && [targetHostID caseInsensitiveCompare:machineID] == NSOrderedSame) {
LOGD(@"Targeted rule_sync for host_id: %@", targetHostID);
self.targetedRuleSync = YES;
[self ruleSync];
} else {
uint32_t delaySeconds = arc4random_uniform((uint32_t)self.FCMGlobalRuleSyncDeadline);
LOGD(@"Global rule_sync, staggering: %u second delay", delaySeconds);
[self ruleSyncSecondsFromNow:delaySeconds];
}
} else if ([action isEqualToString:kConfigSync]) {
[self fullSync];
} else if ([action isEqualToString:kLogSync]) {
[self fullSync];
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
- (NSData *)messageDataFromFCMmessage:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
if (!messageData) {
LOGD(@"Unable to parse push notification message data");
return nil;
}
NSError *error;
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!rawMessage) {
LOGD(@"Unable to parse push notification message data: %@", error);
return nil;
}
// Create a new message dropping unexpected values
NSArray *allowedKeys = @[ kFCMActionKey, kFCMFileHashKey, kFCMFileNameKey, kFCMTargetHostIDKey ];
NSMutableDictionary *message = [NSMutableDictionary dictionaryWithCapacity:allowedKeys.count];
for (NSString *key in allowedKeys) {
if ([rawMessage[key] isKindOfClass:[NSString class]] && [rawMessage[key] length]) {
message[key] = rawMessage[key];
}
}
return message.count ? [message copy] : nil;
}
#pragma mark sync timer control
- (void)fullSync {
[self fullSyncSecondsFromNow:0];
}
- (void)fullSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kFullSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kFullSync);
return;
}
- (void)syncSecondsFromNow:(uint64_t)seconds {
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)syncAndMakeItClean:(BOOL)clean withReply:(void (^)(SNTSyncStatusType))reply {
if (dispatch_semaphore_wait(self.syncLimiter, DISPATCH_TIME_NOW)) {
if (reply) reply(SNTSyncStatusTypeTooManySyncsInProgress);
return;
}
dispatch_async(self.syncQueue, ^() {
SLOGI(@"Starting sync...");
if (clean) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] setSyncCleanRequired:YES
reply:^() {
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
SLOGE(@"Timeout waiting for daemon");
if (reply) reply(SNTSyncStatusTypeDaemonTimeout);
return;
}
}
if (reply) reply(SNTSyncStatusTypeSyncStarted);
SNTSyncStatusType status = [self preflight];
if (reply) reply(status);
dispatch_semaphore_signal(self.syncLimiter);
});
}
- (void)ruleSync {
[self ruleSyncSecondsFromNow:0];
}
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kRuleSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kRuleSync);
return;
}
[self rescheduleTimerQueue:self.ruleSyncTimer secondsFromNow:seconds];
}
@@ -344,86 +207,105 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
uint64_t leeway = (seconds * 0.5) * NSEC_PER_SEC;
dispatch_source_set_timer(timerQueue, dispatch_walltime(NULL, interval), interval, leeway);
}
- (void)ruleSyncImpl {
// Rule only syncs are exclusivly scheduled by self.ruleSyncTimer. We do not need to worry about
// using self.syncLimiter here. However we do want to do the work on self.syncQueue so we do not
// overlap with a full sync.
dispatch_async(self.syncQueue, ^() {
if (![[SNTConfigurator configurator] syncBaseURL]) return;
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
LOGE(@"Rule sync failed to create sync state: %ld", status);
return;
}
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
BOOL ret = [p sync];
LOGD(@"Rule download %@", ret ? @"complete" : @"failed");
});
}
- (void)preflightSync {
[self preflightOnly:YES];
}
#pragma mark syncing chain
- (void)preflight {
[self preflightOnly:NO];
- (SNTSyncStatusType)preflight {
return [self preflightOnly:NO];
}
- (void)preflightOnly:(BOOL)preflightOnly {
LOGD(@"Preflight starting");
SNTSyncState *syncState = [self createSyncState];
- (SNTSyncStatusType)preflightOnly:(BOOL)preflightOnly {
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
return status;
}
SLOGD(@"Preflight starting");
SNTSyncPreflight *p = [[SNTSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
SLOGD(@"Preflight complete");
// Clean up reachability if it was started for a non-network error
[self stopReachability];
self.eventBatchSize = syncState.eventBatchSize;
// Start listening for push notifications with a full sync every FCMFullSyncInterval
if (syncState.daemon && [SNTConfigurator configurator].fcmEnabled) {
self.FCMFullSyncInterval = syncState.FCMFullSyncInterval;
self.FCMGlobalRuleSyncDeadline = syncState.FCMGlobalRuleSyncDeadline;
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCM not enabled. Sync every %lu min.", syncState.fullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.fullSyncInterval = syncState.fullSyncInterval;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.fullSyncInterval];
// Start listening for push notifications with a full sync every
// pushNotificationsFullSyncInterval.
if ([SNTConfigurator configurator].fcmEnabled) {
[self.pushNotifications listenWithSyncState:syncState];
} else {
LOGD(@"Push notifications are not enabled. Sync every %lu min.",
syncState.fullSyncInterval / 60);
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:syncState.fullSyncInterval];
}
if (preflightOnly) return;
if (preflightOnly) return SNTSyncStatusTypeSuccess;
return [self eventUploadWithSyncState:syncState];
} else {
if (!syncState.daemon) {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
return SNTSyncStatusTypePreflightFailed;
}
- (void)eventUploadWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Event upload starting");
- (SNTSyncStatusType)eventUploadWithSyncState:(SNTSyncState *)syncState {
SLOGD(@"Event upload starting");
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
SLOGD(@"Event upload complete");
return [self ruleDownloadWithSyncState:syncState];
} else {
LOGE(@"Event upload failed, aborting run");
if (!syncState.daemon) exit(1);
}
SLOGE(@"Event upload failed, aborting run");
return SNTSyncStatusTypeEventUploadFailed;
}
- (void)ruleDownloadWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Rule download starting");
- (SNTSyncStatusType)ruleDownloadWithSyncState:(SNTSyncState *)syncState {
SLOGD(@"Rule download starting");
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
SLOGD(@"Rule download complete");
return [self postflightWithSyncState:syncState];
} else {
LOGE(@"Rule download failed, aborting run");
if (!syncState.daemon) exit(1);
}
SLOGE(@"Rule download failed, aborting run");
return SNTSyncStatusTypeRuleDownloadFailed;
}
- (void)postflightWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Postflight starting");
- (SNTSyncStatusType)postflightWithSyncState:(SNTSyncState *)syncState {
SLOGD(@"Postflight starting");
SNTSyncPostflight *p = [[SNTSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
if (!syncState.daemon) exit(0);
} else {
LOGE(@"Postflight failed");
if (!syncState.daemon) exit(1);
SLOGD(@"Postflight complete");
SLOGI(@"Sync completed successfully");
return SNTSyncStatusTypeSuccess;
}
SLOGE(@"Postflight failed");
return SNTSyncStatusTypePostflightFailed;
}
#pragma mark internal helpers
@@ -437,29 +319,31 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
return timerQueue;
}
- (SNTSyncState *)createSyncState {
- (SNTSyncState *)createSyncStateWithStatus:(SNTSyncStatusType *)status {
// Gather some data needed during some sync stages
SNTSyncState *syncState = [[SNTSyncState alloc] init];
SNTConfigurator *config = [SNTConfigurator configurator];
syncState.syncBaseURL = config.syncBaseURL;
if (syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (!syncState.daemon) exit(1);
SLOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (*status) *status = SNTSyncStatusTypeMissingSyncBaseURL;
return nil;
} else if (![syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
SLOGW(@"SyncBaseURL is not over HTTPS!");
}
syncState.machineID = config.machineID;
if (syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
if (!syncState.daemon) exit(1);
SLOGE(@"Missing Machine ID. Can't sync without it.");
if (*status) *status = SNTSyncStatusTypeMissingMachineID;
return nil;
}
syncState.machineOwner = config.machineOwner;
if (syncState.machineOwner.length == 0) {
syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
SLOGW(@"Missing Machine Owner.");
}
dispatch_group_t group = dispatch_group_create();
@@ -482,7 +366,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
SLOGD(@"%@", line);
};
// Configure server auth
@@ -504,36 +388,23 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
syncState.compressedContentEncoding =
config.enableBackwardsCompatibleContentEncoding ? @"zlib" : @"deflate";
syncState.FCMToken = self.FCMToken;
syncState.pushNotificationsToken = self.pushNotifications.token;
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return syncState;
}
- (void)lockAction:(NSString *)action {
[self.dispatchLock setObject:@YES forKey:action];
}
- (void)unlockAction:(NSString *)action {
[self.dispatchLock removeObjectForKey:action];
}
- (BOOL)checkLockAction:(NSString *)action {
return ([self.dispatchLock objectForKey:action] == nil);
}
#pragma mark reachability methods
- (void)setReachable:(BOOL)reachable {
_reachable = reachable;
if (reachable) {
[self stopReachability];
[self fullSync];
[self sync];
}
}

View File

@@ -16,7 +16,6 @@
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncState.h"

View File

@@ -22,6 +22,7 @@
#import "Source/common/SNTSystemInfo.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTSyncPreflight
@@ -40,7 +41,9 @@
requestDict[kModelIdentifier] = [SNTSystemInfo modelIdentifier];
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kPrimaryUser] = self.syncState.machineOwner;
if (self.syncState.FCMToken) requestDict[kFCMToken] = self.syncState.FCMToken;
if (self.syncState.pushNotificationsToken) {
requestDict[kFCMToken] = self.syncState.pushNotificationsToken;
}
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
@@ -72,11 +75,15 @@
dispatch_group_leave(group);
}];
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
// Stop the sync if we are unable to communicate with daemon.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
SLOGE(@"Unable to communicate with daemon.");
return NO;
}
// If user requested it or we've never had a successful sync, try from a clean slate.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] || syncClean) {
LOGD(@"Clean sync requested by user");
if (syncClean) {
SLOGD(@"Clean sync requested by user");
requestDict[kRequestCleanSync] = @YES;
}
@@ -103,15 +110,23 @@
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
NSNumber *enableAllEventUpload = resp[kEnableAllEventUpload];
[[self.daemonConn remoteObjectProxy] setEnableAllEventUpload:[enableAllEventUpload boolValue]
reply:^{
dispatch_group_leave(group);
}];
self.syncState.eventBatchSize = [resp[kBatchSize] unsignedIntegerValue] ?: kDefaultEventBatchSize;
// Don't let these go too low
NSUInteger FCMIntervalValue = [resp[kFCMFullSyncInterval] unsignedIntegerValue];
self.syncState.FCMFullSyncInterval =
(FCMIntervalValue < kDefaultFullSyncInterval) ? kDefaultFCMFullSyncInterval : FCMIntervalValue;
self.syncState.pushNotificationsFullSyncInterval = (FCMIntervalValue < kDefaultFullSyncInterval)
? kDefaultPushNotificationsFullSyncInterval
: FCMIntervalValue;
FCMIntervalValue = [resp[kFCMGlobalRuleSyncDeadline] unsignedIntegerValue];
self.syncState.FCMGlobalRuleSyncDeadline =
(FCMIntervalValue < 60) ? kDefaultFCMGlobalRuleSyncDeadline : FCMIntervalValue;
self.syncState.pushNotificationsGlobalRuleSyncDeadline =
(FCMIntervalValue < 60) ? kDefaultPushNotificationsGlobalRuleSyncDeadline : FCMIntervalValue;
// Check if our sync interval has changed
NSUInteger intervalValue = [resp[kFullSyncInterval] unsignedIntegerValue];
@@ -144,7 +159,7 @@
}
if ([resp[kCleanSync] boolValue]) {
LOGD(@"Clean sync requested by server");
SLOGD(@"Clean sync requested by server");
self.syncState.cleanSync = YES;
}

View File

@@ -13,13 +13,15 @@
/// limitations under the License.
#import "Source/santasyncservice/SNTSyncRuleDownload.h"
#include "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTSyncRuleDownload
@@ -46,13 +48,13 @@
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC))) {
LOGE(@"Failed to add rule(s) to database: timeout sending rules to daemon");
SLOGE(@"Failed to add rule(s) to database: timeout sending rules to daemon");
return NO;
}
if (error) {
LOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
LOGD(@"Failure reason: %@", error.localizedFailureReason);
SLOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
SLOGD(@"Failure reason: %@", error.localizedFailureReason);
return NO;
}
@@ -64,14 +66,11 @@
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
LOGI(@"Processed %lu rules", newRules.count);
SLOGI(@"Processed %lu rules", newRules.count);
// Send out push notifications about any newly allowed binaries
// that had been previously blocked by santad.
[self.syncState.allowlistNotificationQueue addOperationWithBlock:^{
[self announceUnblockingRules:newRules];
}];
[self announceUnblockingRules:newRules];
return YES;
}
@@ -99,7 +98,7 @@
count++;
}
}
LOGI(@"Received %u rules", count);
SLOGI(@"Received %u rules", count);
cursor = response[kCursor];
} while (cursor);
return newRules;
@@ -108,28 +107,25 @@
// Send out push notifications for allowed bundles/binaries whose rule download was preceded by
// an associated announcing FCM message.
- (void)announceUnblockingRules:(NSArray<SNTRule *> *)newRules {
if (!self.syncState.targetedRuleSync) return;
NSMutableArray *processed = [NSMutableArray array];
SNTPushNotificationsTracker *tracker = [SNTPushNotificationsTracker tracker];
[[tracker all]
enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *notifier, BOOL *stop) {
// Each notifier object is a dictionary with name and count keys. If the count has been
// decremented to zero, then this means that we have downloaded all of the rules associated
// with this SHA256 hash (which might be a bundle hash or a binary hash), in which case we are
// OK to show a notification that the named bundle/binary can be run.
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining && [remaining intValue] == 0) {
[processed addObject:key];
NSString *message = [NSString stringWithFormat:@"%@ can now be run", notifier[kFileName]];
[[self.daemonConn remoteObjectProxy] postRuleSyncNotificationWithCustomMessage:message
reply:^{
}];
}
}];
for (NSString *key in self.syncState.allowlistNotifications) {
// Each notifier object is a dictionary with name and count keys. If the count has been
// decremented to zero, then this means that we have downloaded all of the rules associated with
// this SHA256 hash (which might be a bundle hash or a binary hash), in which case we are OK to
// show a notification that the named bundle/binary can be run.
NSDictionary *notifier = self.syncState.allowlistNotifications[key];
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining && [remaining intValue] == 0) {
[processed addObject:key];
NSString *message = [NSString stringWithFormat:@"%@ can now be run", notifier[kFileName]];
[[self.daemonConn remoteObjectProxy] postRuleSyncNotificationWithCustomMessage:message
reply:^{
}];
}
}
// Remove all entries from allowlistNotifications dictionary that had zero count.
[self.syncState.allowlistNotifications removeObjectsForKeys:processed];
[tracker removeNotificationsForHashes:processed];
}
// Converts rule information downloaded from the server into a SNTRule. Because any information
@@ -188,30 +184,11 @@
primaryHash = newRule.identifier;
}
// As we read in rules, we update the "remaining count" information stored in
// allowlistNotifications. This count represents the number of rules associated with the primary
// hash that still need to be downloaded and added.
[self.syncState.allowlistNotificationQueue addOperationWithBlock:^{
NSMutableDictionary *notifier = self.syncState.allowlistNotifications[primaryHash];
if (notifier) {
NSNumber *ruleCount = dict[kFileBundleBinaryCount];
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining) { // bundle rule with existing count
// If the primary hash already has an associated count field, just decrement it.
notifier[kFileBundleBinaryCount] = @([remaining intValue] - 1);
} else if (ruleCount) { // bundle rule seen for first time
// Downloaded rules including count information are associated with bundles.
// The first time we see a rule for a given bundle hash, add a count field with an
// initial value equal to the number of associated rules, then decrement this value by 1
// to account for the rule that we've just downloaded.
notifier[kFileBundleBinaryCount] = @([ruleCount intValue] - 1);
} else { // non-bundle binary rule
// Downloaded rule had no count information, meaning it is a singleton non-bundle rule.
// Therefore there are no more rules associated with this hash to download.
notifier[kFileBundleBinaryCount] = @0;
}
}
}];
// As we read in rules, we update the "remaining count" information. This count represents the
// number of rules associated with the primary hash that still need to be downloaded and added.
[[SNTPushNotificationsTracker tracker]
decrementPendingRulesForHash:primaryHash
totalRuleCount:dict[kFileBundleBinaryCount]];
}
return newRule;

View File

@@ -1,4 +1,4 @@
/// Copyright 2020 Google Inc. All rights reserved.
/// 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.
@@ -12,22 +12,96 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTSyncService.h"
#import "Source/santasyncservice/SNTSyncService.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTSyncBroadcaster.h"
#import "Source/santasyncservice/SNTSyncManager.h"
@interface SNTSyncService ()
@property(nonatomic, readonly) SNTSyncManager *syncManager;
@property(nonatomic, readonly) MOLXPCConnection *daemonConn;
@property(nonatomic, readonly) NSMutableArray *logListeners;
@end
// TODO(bur): Add "santactl sync --daemon" behavior here.
@implementation SNTSyncService
- (instancetype)init {
self = [super init];
if (self) {
_logListeners = [NSMutableArray array];
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^(void) {
// Spindown this process if we can't establish a connection
// or if the daemon is killed or crashes.
// If we are needed we will be re-launched.
[self spindown];
};
[daemonConn resume];
// Ensure we have no privileges
if (!DropRootPrivileges()) {
LOGE(@"Failed to drop root privileges. Exiting.");
exit(1);
}
// 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]];
_daemonConn = daemonConn;
_syncManager = [[SNTSyncManager alloc] initWithDaemonConnection:daemonConn];
// This service should only start up if com.google.santa.daemon
// noticed there is sync server configured and established a connection
// with us. Go ahead and start syncing!
[_syncManager syncSecondsFromNow:15];
}
return self;
}
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle {
[self.syncManager postEventsToSyncServer:events fromBundle:fromBundle];
}
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply {
[self.syncManager postBundleEventToSyncServer:event reply:reply];
}
- (void)isFCMListening:(void (^)(BOOL))reply {
[self.syncManager isFCMListening:reply];
}
- (void)performFullSyncWithReply:(SNTFullSyncReplyBlock)reply {
// TODO(bur): Add support for santactl sync --debug to enable debug logging for that sync.
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
isClean:(BOOL)cleanSync
reply:(void (^)(SNTSyncStatusType))reply {
MOLXPCConnection *ll = [[MOLXPCConnection alloc] initClientWithListener:logListener];
ll.remoteInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceLogReceiverXPC)];
[ll resume];
[self.syncManager syncAndMakeItClean:cleanSync
withReply:^(SNTSyncStatusType status) {
if (status == SNTSyncStatusTypeSyncStarted) {
[[SNTSyncBroadcaster broadcaster] addLogListener:ll];
return;
}
[[SNTSyncBroadcaster broadcaster] barrier];
[[SNTSyncBroadcaster broadcaster] removeLogListener:ll];
reply(status);
}];
}
- (void)spindown {
LOGI(@"Spinning down.");
exit(0);
}
@end

View File

@@ -20,6 +20,7 @@
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/NSData+Zlib.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@interface SNTSyncStage ()
@@ -59,7 +60,7 @@
NSError *error;
requestBody = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error];
if (error) {
LOGD(@"Failed to encode JSON request: %@", error);
SLOGD(@"Failed to encode JSON request: %@", error);
return nil;
}
}
@@ -93,7 +94,7 @@
nanosleep(&ts, NULL);
}
LOGD(@"Performing request, attempt %d", attempt);
SLOGD(@"Performing request, attempt %d", attempt);
data = [self performRequest:request timeout:timeout response:&response error:&error];
if (response.statusCode == 200) break;
@@ -130,7 +131,7 @@
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[self stripXssi:data]
options:0
error:&error];
if (error) LOGD(@"Failed to decode JSON response: %@", error);
if (error) SLOGD(@"Failed to decode JSON response: %@", error);
return dict ?: @{};
}
@@ -202,10 +203,10 @@
reply:^{
}];
self.syncState.xsrfToken = headers[kXSRFToken];
LOGD(@"Retrieved new XSRF token");
SLOGD(@"Retrieved new XSRF token");
success = YES;
} else {
LOGD(@"Failed to retrieve XSRF token");
SLOGD(@"Failed to retrieve XSRF token");
}
};
return success;

View File

@@ -35,18 +35,20 @@
/// An XSRF token to send in the headers with each request.
@property NSString *xsrfToken;
/// Full sync interval in seconds without FCM to update kDefaultFullSyncInterval => when FCM
/// is not used, defaults to 10m.
/// Full sync interval in seconds, defaults to kDefaultFullSyncInterval. If push notifications are
/// being used this interval will be ignored in favor of pushNotificationsFullSyncInterval.
@property NSUInteger fullSyncInterval;
/// An FCM token to subscribe to push notifications.
@property(copy) NSString *FCMToken;
/// An token to subscribe to push notifications.
@property(copy) NSString *pushNotificationsToken;
/// Full sync interval in seconds while listening for FCM messages.
@property NSUInteger FCMFullSyncInterval;
/// Full sync interval in seconds while listening for push notifications, defaults to
/// kDefaultPushNotificationsFullSyncInterval.
@property NSUInteger pushNotificationsFullSyncInterval;
/// Leeway time in seconds when receiving a global rule sync message.
@property NSUInteger FCMGlobalRuleSyncDeadline;
/// Leeway time in seconds when receiving a global rule sync push notification, defaults to
/// kDefaultPushNotificationsGlobalRuleSyncDeadline.
@property NSUInteger pushNotificationsGlobalRuleSyncDeadline;
/// Machine identifier and owner.
@property(copy) NSString *machineID;
@@ -69,18 +71,6 @@
/// Array of bundle IDs to find binaries for.
@property NSArray *bundleBinaryRequests;
/// Returns YES if the santactl session is running as a daemon, returns NO otherwise.
@property BOOL daemon;
/// Returns YES if the session is targeted for this machine, returns NO otherwise.
@property BOOL targetedRuleSync;
/// Reference to the sync manager's ruleSyncCache. Used to lookup binary names for notifications.
@property(weak) NSMutableDictionary *allowlistNotifications;
/// Reference to the serial operation queue used for accessing allowlistNotifications.
@property(weak) NSOperationQueue *allowlistNotificationQueue;
/// The header value for ContentEncoding when sending compressed content.
/// Either "deflate" (default) or "zlib".
@property(copy) NSString *compressedContentEncoding;

View File

@@ -140,6 +140,19 @@
return [NSData dataWithContentsOfFile:path];
}
- (void)setupDefaultDaemonConnResponses {
OCMStub([self.daemonConnRop
databaseRuleCounts:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(0), // binary
OCMOCK_VALUE(0), // cert
OCMOCK_VALUE(0), // compiler
OCMOCK_VALUE(0), // transitive
OCMOCK_VALUE(0), // teamID
nil])]);
OCMStub([self.daemonConnRop syncCleanRequired:([OCMArg invokeBlockWithArgs:@NO, nil])]);
OCMStub([self.daemonConnRop
clientMode:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(SNTClientModeMonitor), nil])]);
}
#pragma mark - SNTSyncStage Tests
- (void)testBaseFetchXSRFTokenSuccess {
@@ -186,6 +199,7 @@
#pragma mark - SNTSyncPreflight Tests
- (void)testPreflightBasicResponse {
[self setupDefaultDaemonConnResponses];
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_basic.json"];
@@ -199,6 +213,7 @@
}
- (void)testPreflightBlockUSBMount {
[self setupDefaultDaemonConnResponses];
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_toggle_blockusb.json"];
@@ -239,9 +254,16 @@
- (void)testPreflightCleanSync {
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
id processInfoMock = OCMClassMock([NSProcessInfo class]);
OCMStub([processInfoMock processInfo]).andReturn(processInfoMock);
[OCMStub([processInfoMock arguments]) andReturn:@[ @"xctest", @"--clean" ]];
OCMStub([self.daemonConnRop
databaseRuleCounts:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(0), // binary
OCMOCK_VALUE(0), // cert
OCMOCK_VALUE(0), // compiler
OCMOCK_VALUE(0), // transitive
OCMOCK_VALUE(0), // teamID
nil])]);
OCMStub([self.daemonConnRop
clientMode:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(SNTClientModeMonitor), nil])]);
OCMStub([self.daemonConnRop syncCleanRequired:([OCMArg invokeBlockWithArgs:@YES, nil])]);
NSData *respData = [self dataFromDict:@{kCleanSync : @YES}];
[self stubRequestBody:respData
@@ -259,6 +281,7 @@
}
- (void)testPreflightLockdown {
[self setupDefaultDaemonConnResponses];
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_lockdown.json"];

View File

@@ -1,89 +0,0 @@
#!/bin/sh
set -e
GIT_ROOT=$(git rev-parse --show-toplevel)
TMP_DIR=$(mktemp -d)
function cleanup() {
# Reset randomize_version if we used it
if [ -f "$TMP_DIR/version.bzl" ]; then
mv "$TMP_DIR/version.bzl" $VERSION_FILE
fi
rm -rf $TMP_DIR
rm -f $GIT_ROOT/bazel-bin/santa-*.tar.gz
}
trap cleanup EXIT
function randomize_version() {
VERSION_FILE="$GIT_ROOT/version.bzl"
# Create a random version ID for the generated Santa version.
# The system extension won't replace itself if the version string isn't different than the one
# presently installed.
cp $VERSION_FILE $TMP_DIR
RANDOM_VERSION="$RANDOM.$RANDOM"
echo "Setting version to $RANDOM_VERSION"
echo "SANTA_VERSION = \"$RANDOM_VERSION\"" > $VERSION_FILE
}
function build_custom_signed() {
SANTAD_PATH=Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon
SANTA_BIN_PATH=Santa.app/Contents/MacOS
KEYCHAIN="santa-dev-test.keychain"
SANTAD_ENTITLEMENTS="$GIT_ROOT/Source/santad/com.google.santa.daemon.systemextension.entitlements"
SIGNING_IDENTITY="localhost"
bazel build --apple_generate_dsym -c opt --define=SANTA_BUILD_TYPE=ci --define=apple.propagate_embedded_extra_outputs=yes --macos_cpus=x86_64,arm64 //:release
echo "> Build complete, installing santa"
tar xvf $GIT_ROOT/bazel-bin/santa-*.tar.gz -C $TMP_DIR
CS_ARGS="--prefix=EQHXZ8M8AV -fs $SIGNING_IDENTITY --timestamp --options library,kill,runtime"
for bin in $TMP_DIR/binaries/$SANTA_BIN_PATH/*; do
codesign --keychain $KEYCHAIN --preserve-metadata=entitlements ${CS_ARGS} $bin
done
codesign ${CS_ARGS} --keychain $KEYCHAIN --entitlements $SANTAD_ENTITLEMENTS $TMP_DIR/binaries/$SANTAD_PATH
}
function build_provisionprofile_signed() {
bazel build --apple_generate_dsym -c opt --define=SANTA_BUILD_TYPE=release --define=apple.propagate_embedded_extra_outputs=yes --macos_cpus=x86_64,arm64 //:release
tar xvf $GIT_ROOT/bazel-bin/santa-*.tar.gz -C $TMP_DIR
}
function build() {
SANTA_DAEMON_PROVPROFILE=$GIT_ROOT/Source/santad/Santa_Daemon_Dev.provisionprofile
SANTA_PROVPROFILE=$GIT_ROOT/Source/santa/Santa_Dev.provisionprofile
if [[ -f $SANTA_DAEMON_PROVPROFILE && -f $SANTA_PROVPROFILE ]]; then
echo "Using provisionprofiles in $SANTA_DAEMON_PROVPROFILE and $SANTA_PROVPROFILE"
build_provisionprofile_signed
else
echo "No provisionprofiles detected, creating self-signed certs"
build_custom_signed
fi
}
function install() {
echo "> Running install.sh"
(
cd $TMP_DIR
sudo ./conf/install.sh
)
}
function main() {
for i in "$@"; do
case $i in
--randomize_version)
randomize_version
;;
esac
done
build
install
}
main $@
exit $?

View File

@@ -1,41 +0,0 @@
#!/bin/sh
set -e
GIT_ROOT=$(git rev-parse --show-toplevel)
CNF_PATH=$GIT_ROOT/Testing/openssl.cnf
KEYCHAIN="santa-dev-test.keychain"
function init() {
openssl genrsa -out ./santa.key 2048
openssl rsa -in ./santa.key -out ./santa.key
openssl req -new -key ./santa.key -out ./santa.csr -config $CNF_PATH
openssl x509 -req -days 10 -in ./santa.csr -signkey ./santa.key -out ./santa.crt -extfile $CNF_PATH -extensions codesign
openssl pkcs12 -export -out santa.p12 -inkey santa.key -in santa.crt -password pass:santa
security create-keychain -p santa $KEYCHAIN
security import ./santa.p12 -k $KEYCHAIN -A -P santa
security add-trusted-cert -d -r trustRoot -k $KEYCHAIN santa.crt
}
function cleanup() {
security delete-keychain $KEYCHAIN
rm santa.key
rm santa.csr
rm santa.p12
}
function main() {
case $1 in
init)
init
;;
cleanup)
cleanup
;;
*)
echo "$0 [init|cleanup]"
;;
esac
}
main $@
exit $?

View File

@@ -1,25 +0,0 @@
#!/bin/bash
set -e
set -x
GIT_ROOT=$(git rev-parse --show-toplevel)
run_tests() {
(
local -a TEST_FLAGS=( --strategy=TestRunner=standalone --test_output=all )
cd $GIT_ROOT/Testing/integration
time bazel test "${TEST_FLAGS[@]}" -- ...
)
}
setup() {
$GIT_ROOT/Testing/start_env.sh
sudo santactl sync --debug
}
main() {
setup
run_tests
}
main "$@"

View File

@@ -1,22 +0,0 @@
[ ca ]
default_ca = CA_default
[ req ]
prompt = no
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
commonName = localhost
countryName = US
organizationName = Google LLC
OU=EQHXZ8M8AV
name = santa
[ codesign ]
keyUsage = digitalSignature
extendedKeyUsage = codeSigning
[ v3_ca ]
basicConstraints = critical,CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always

View File

@@ -1,6 +0,0 @@
#!/bin/sh
killall moroz
security delete-identity -c "localhost"
rm -rf /Applications/Santa.app
systemextensionsctl reset
security delete-keychain santa-dev-test.keychain

View File

@@ -1,47 +0,0 @@
#!/bin/sh
set -e
GIT_ROOT=$(git rev-parse --show-toplevel)
SANTA_BIN_PATH=Santa.app/Contents/MacOS
SIGNING_IDENTITY="localhost"
function setup_certs() {
echo "> Creating codesigning certs and keys"
$GIT_ROOT/Testing/init_dev_certs.sh init
}
function run_moroz() {
echo "> Running moroz in the background"
go get github.com/groob/moroz/cmd/moroz
~/go/bin/moroz -configs="$GIT_ROOT/Testing/global.toml" -tls-key santa.key -tls-cert santa.crt &
}
function install_profile() {
echo "> Installing mobileconfig"
# The `profiles` tool has been deprecated as of Big Sur. Ugly workaround instead:
sudo open /System/Library/PreferencePanes/Profiles.prefPane "$GIT_ROOT/Testing/com.google.santa.mobileconfig"
}
function build_install_santa() {
echo "> Building and signing Santa"
$GIT_ROOT/Testing/build_and_sign.sh
systemextensionsctl list
# install.sh _should_ already start the system extension, but we want to
# explicitly call `--load-system-extension` again to actually log loading
# failures.
echo "> Install complete, attempting to explicitly start the santa systemextension"
/Applications/$SANTA_BIN_PATH/Santa --load-system-extension
systemextensionsctl list
}
function main() {
install_profile
setup_certs
run_moroz
build_install_santa
}
main $@
exit $?

View File

@@ -8,7 +8,7 @@ load(
git_repository(
name = "build_bazel_rules_apple",
commit = "4246cfe864953025cdaa105d8105679fcd1fba29", # Latest commit that fixes https://github.com/google/santa/issues/1358
commit = "7115f0188d141d57d64a6875735847c975956dae", # 0.34.0
remote = "https://github.com/bazelbuild/rules_apple.git",
)

View File

@@ -24,8 +24,8 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
| ClientMode\* | Integer | 1 = MONITOR, 2 = LOCKDOWN, defaults to MONITOR |
| FailClosed | Bool | If true and the ClientMode is LOCKDOWN: execution will be denied when there is an error reading or processing an executable file. |
| FileChangesRegex\* | String | The regex of paths to log file changes. Regexes are specified in ICU format. |
| AllowedPathRegex\* | String | A regex to allow if the binary or certificate scopes did not allow/block execution. Regexes are specified in ICU format. |
| BlockedPathRegex\* | String | A regex to block if the binary or certificate scopes did not allow/block an execution. Regexes are specified in ICU format. |
| AllowedPathRegex\* | String | A regex to allow if the binary, certificate, or Team ID scopes did not allow/block execution. Regexes are specified in ICU format. |
| BlockedPathRegex\* | String | A regex to block if the binary, certificate, or Team ID scopes did not allow/block an execution. Regexes are specified in ICU format. |
| EnableBadSignatureProtection | Bool | Enable bad signature protection, defaults to NO. If this flag is set to YES, binaries with a bad signing chain will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
| EnablePageZeroProtection | Bool | Enable `__PAGEZERO` protection, defaults to YES. If this flag is set to YES, 32-bit binaries that are missing the `__PAGEZERO` segment will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
| EnableSysxCache | Bool | Enables a secondary cache that ensures better performance when multiple EndpointSecurity system extensions are installed. Defaults to YES in 2021.8, defaults to NO in earlier versions. |
@@ -39,6 +39,7 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
| ModeNotificationLockdown | String | The notification text to display when the client goes into Lockdown mode. Defaults to "Switching into Lockdown mode". |
| SyncBaseURL | String | The base URL of the sync server. |
| SyncProxyConfiguration | Dictionary | The proxy configuration to use when syncing. See the [Apple Documentation](https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants) for details on the keys that can be used in this dictionary. |
| SyncEnableCleanSyncEventUpload | Bool | If true, events will be uploaded to the sync server even if a clean sync is requested. Defaults to false. |
| ClientAuthCertificateFile | String | If set, this contains the location of a PKCS#12 certificate to be used for sync authentication. |
| ClientAuthCertificatePassword | String | Contains the password for the PKCS#12 certificate. |
| ClientAuthCertificateCN | String | If set, this is the Common Name of a certificate in the System keychain to be used for sync authentication. The corresponding private key must also be in the keychain. |
@@ -51,7 +52,7 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
| MachineOwnerKey | String | The key to use on MachineOwnerPlist. |
| MachineIDPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
| MachineIDKey | String | The key to use on MachineIDPlist. |
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. 3) protobuf (BETA): Sent to file on disk using maildir format. Defaults to filelog. |
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. 3) protobuf (BETA): Sent to file on disk using maildir format. 4) null: Don't output any event logs. Defaults to filelog. |
| EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. |
| MailDirectory | String | If EventLogType is set to protobuf, MailDirectory will provide the the base directory used to save files according to the maildir format. Defaults to /var/db/santa/mail. |
| MailDirectoryFileSizeThresholdKB | Integer | If EventLogType is set to protobuf, MailDirectoryFileSizeThresholdKB defines the per-file size limit for files stored in the mail directory. Events are buffered in memory until this threshold would be exceeded (or MailDirectoryEventMaxFlushTimeSec is exceeded). Defaults to 100. |
@@ -63,9 +64,10 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
| MetricExportInterval | Integer | Number of seconds to wait between exporting metrics. Defaults to 30. |
| MetricExportTimeout | Integer | Number of seconds to wait before a timeout occurs when exporting metrics. Defaults to 30. |
| MetricExtraLabels | Dictionary | A map of key value pairs to add to all metric root labels. (e.g. a=b,c=d) defaults to @{}). If a previously set key (e.g. host_name is set to "" then the key is remove from the metric root labels. Alternatively if a value is set for an existing key then the new value will override the old. |
| EnableAllEventUpload | Bool | If YES, the client will upload all execution events to the sync server, including those that were explicitly allowed. |
*overridable by the sync server: run `santactl status` to check the current
\*overridable by the sync server: run `santactl status` to check the current
running config
##### EventDetailURL
@@ -76,14 +78,16 @@ take them to a web page with more information about that event.
This property contains a kind of format string to be turned into the URL to send
them to. The following sequences will be replaced in the final URL:
| Key | Description |
| ------------ | ---------------------------------------- |
| %file_sha% | SHA-256 of the file that was blocked |
| %machine_id% | ID of the machine |
| %username% | The executing user |
| %serial% | System's serial number |
| %uuid% | System's UUID |
| %hostname% | System's full hostname |
| Key | Description |
| ------------------------------ | ------------------------------------------------------------------------------ |
| %file_identifier% | SHA-256 of the file that was blocked |
| %bundle\_or\_file\_identifier% | SHA-256 of the file that was blocked or the bundle containing it, if available |
| %file_sha% | Deprecated, acts like bundle\_or\_file\_identifier |
| %machine\_id% | ID of the machine |
| %username% | The executing user |
| %serial% | System's serial number |
| %uuid% | System's UUID |
| %hostname% | System's full hostname |
For example: `https://sync-server-hostname/%machine_id%/%file_sha%`
@@ -208,6 +212,7 @@ ways to install configuration profiles:
| fcm\_global\_rule\_sync\_deadline\* | Integer | The max time to wait before performing a rule sync when a global rule sync FCM message is received. This allows syncing to be staggered for global events to avoid spikes in server load. Defaults to 600 secs (10 min). |
| enable\_bundles\* | Bool | If set to `True` the bundle scanning feature is enabled. Defaults to `False`. |
| enable\_transitive\_rules | Bool | If set to `True` the transitive rule feature is enabled. Defaults to `False`. |
| enable\_all\_event\_upload | Bool | If set to `True` the client will upload events for all executions, including those that are explicitly allowed. |
| block\_usb\_mass\_storage | Bool | If set to 'True' blocking USB Mass storage feature is enabled. Defaults to `False`. |
| remount\_usb\_mode | Array | Array of strings for arguments to pass to mount -o (any of "rdonly", "noexec", "nosuid", "nobrowse", "noowners", "nodev", "async", "-j"). when forcibly remounting devices. No default. |

View File

@@ -7,6 +7,9 @@ redirect_from:
# Binary Authorization Overview
NOTE: This doc is out-dated and will be updated soon. We don't rely on a Kernel
Extension anymore.
#### Background
The decision flow starts in the kernel. The macOS kernel is extensible by way of
@@ -14,10 +17,7 @@ a kernel extension (KEXT). macOS makes available kernel programming interfaces
(KPIs) to be used by a KEXT. Santa utilizes the Kernel Authorization (Kauth)
KPI. This is a very powerful and verbose interface that gives Santa the ability
to listen in on most vnode and file systems operations and to take actions,
directly or indirectly, on the operations being performed. Still, there are some
limitations to Kauth which are pointed out in the santa-driver document. For
more information on the santa-driver KEXT see the
[santa-driver.md](../details/santa-driver.md) document.
directly or indirectly, on the operations being performed.
#### Flow of an execve()
@@ -49,9 +49,7 @@ documentation. This flow does not cover the logging component of Santa, see the
`execve()` the same `vnode_id`, santa-driver will have that thread wait
for the in-flight decision from santad. All subsequent `execve()`s for
the same `vnode_id` will use the decision in the cache as explained
in #2, until the cache is invalidated. See the
[santa-driver.md](../details/santa-driver.md) document for more details
on the cache invalidation.
in #2, until the cache is invalidated.
* If the executing file is written to while any of the threads are waiting
for a response the `ACTION_REQUEST_BINARY` entry is removed, forcing the
decision-making process to be restarted.

View File

@@ -1,39 +1,27 @@
#!/bin/sh
GIT_ROOT=$(git rev-parse --show-toplevel)
PROFILE_PATH="$GIT_ROOT/CoverageData"
COV_FILE="$PROFILE_PATH/info.lcov"
function build() {
tests=$(bazel query "tests(//:unit_tests)")
for t in $tests; do
profname=$(echo $t | shasum | awk '{print $1}')
bazel coverage \
--test_env="LLVM_PROFILE_FILE=$PROFILE_PATH/$profname.profraw" \
--experimental_use_llvm_covmap \
--spawn_strategy=standalone \
--cache_test_results=no \
--test_env=LCOV_MERGER=/usr/bin/true \
$t
done
xcrun llvm-profdata merge $PROFILE_PATH/*.profraw -output "$PROFILE_PATH/default.profdata"
}
function generate_lcov() {
object_files=$(find -L $(bazel info bazel-bin) -type f -exec file -L {} \; | grep "Mach-O" | sed 's,:.*,,' | grep -v 'testdata')
bazel_base=$(bazel info execution_root)
true > $COV_FILE
for file in $object_files; do
xcrun llvm-cov export -instr-profile "$PROFILE_PATH/default.profdata" -format=lcov \
--ignore-filename-regex="/Applications/.*|external/.*" \
$file | sed "s,$bazel_base,$GIT_ROOT," >> $COV_FILE
done
}
BAZEL_EXEC_ROOT=$(bazel info execution_root)
COV_FILE="$(bazel info output_path)/_coverage/_coverage_report.dat"
function main() {
mkdir -p $PROFILE_PATH
build
generate_lcov
bazel coverage \
--experimental_use_llvm_covmap \
--instrument_test_targets \
--combined_report=lcov \
--spawn_strategy=standalone \
--test_env=LCOV_MERGER=/usr/bin/true \
//:unit_tests
# The generated file has most of the source files relative to bazel's
# execution_root path, so we strip that off as it prevents files being
# picked up by Coveralls.
sed -i '' "s,${BAZEL_EXEC_ROOT},${GIT_ROOT}," ${COV_FILE}
# We also want to filter out files that aren't ours but which sometimes get
# coverage data created anyway.
sed -i '' '/SF:\/Applications.*/,/end_of_record/d' ${COV_FILE}
sed -i '' '/SF:.*santa\/bazel-out.*/,/end_of_record/d' ${COV_FILE}
}
main

View File

@@ -22,7 +22,7 @@ def santa_unit_test(
srcs = [],
deps = [],
size = "medium",
minimum_os_version = "10.9",
minimum_os_version = "10.15",
resources = [],
structured_resources = [],
copts = [],

15
profiles/BUILD Normal file
View File

@@ -0,0 +1,15 @@
package(
default_visibility = ["//:santa_package_group"],
)
licenses(["notice"])
filegroup(
name="santa_dev",
srcs=["Santa_Dev.provisionprofile"]
)
filegroup(
name="daemon_dev",
srcs=["Santa_Daemon_Dev.provisionprofile"],
)

View File

@@ -1,3 +0,0 @@
"""The version for all Santa components."""
SANTA_VERSION = "2022.3"