mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a67801d5ed | ||
|
|
3d37a3a5ae | ||
|
|
bfae5dc828 | ||
|
|
fde5f52a11 | ||
|
|
01bd1bfdca | ||
|
|
ae13900676 | ||
|
|
a65c91874b | ||
|
|
6a3fda069c | ||
|
|
4d34099142 | ||
|
|
e639574973 | ||
|
|
636f9ea873 | ||
|
|
9099409915 | ||
|
|
976f483a99 | ||
|
|
8a32b7a56b | ||
|
|
7eeb06b406 | ||
|
|
4540a1c656 | ||
|
|
acc7b32b24 | ||
|
|
b92d513f5d | ||
|
|
3458fccd4e | ||
|
|
fdfb00368c | ||
|
|
6bd369cfb2 | ||
|
|
0df26c6214 | ||
|
|
6e22da1d97 | ||
|
|
1725809335 | ||
|
|
3eff49feda | ||
|
|
5caedebb06 | ||
|
|
d823028b72 | ||
|
|
49b2d6e22a | ||
|
|
4236d57e96 | ||
|
|
36d463a1dc | ||
|
|
adbafd6bab | ||
|
|
b5ebe1259c | ||
|
|
e0ae0f481b | ||
|
|
8037c79fc0 | ||
|
|
892d303de1 | ||
|
|
ff3979263e | ||
|
|
01afefd3d4 | ||
|
|
830627e7bc | ||
|
|
601d726fcc | ||
|
|
0be1ca0199 | ||
|
|
8602593149 | ||
|
|
9bca601ce6 | ||
|
|
c73acd59d4 | ||
|
|
3c334e8882 | ||
|
|
5f811cadf8 | ||
|
|
4252475de0 | ||
|
|
45f1822681 | ||
|
|
498a23d907 | ||
|
|
5dff8a18f4 | ||
|
|
676c02626d | ||
|
|
64950d0a99 |
1
.bazelversion
Normal file
1
.bazelversion
Normal file
@@ -0,0 +1 @@
|
||||
5.0.0
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -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
19
BUILD
@@ -1,6 +1,5 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
load("//:version.bzl", "SANTA_VERSION")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
@@ -11,13 +10,14 @@ exports_files(["LICENSE"])
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_label_pattern = "{build}",
|
||||
build_version = SANTA_VERSION + ".{build}",
|
||||
build_label_pattern = ".*santa_{release}\\.{build}",
|
||||
build_version = "{release}.{build}",
|
||||
capture_groups = {
|
||||
"release": "\\d{4}\\.\\d+",
|
||||
"build": "\\d+",
|
||||
},
|
||||
fallback_build_label = "1",
|
||||
short_version_string = SANTA_VERSION,
|
||||
fallback_build_label = "santa_9999.1.1",
|
||||
short_version_string = "{release}",
|
||||
)
|
||||
|
||||
# Used to detect release builds
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,7 @@ readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.s
|
||||
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
|
||||
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
|
||||
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
|
||||
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
|
||||
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
|
||||
|
||||
@@ -64,7 +65,7 @@ readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
|
||||
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
EN="${ENTITLEMENTS}/${BN}.entitlements"
|
||||
|
||||
@@ -113,11 +114,11 @@ echo "verifying signatures"
|
||||
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
|
||||
|
||||
echo "creating fresh release tarball"
|
||||
/bin/mkdir -p "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/binaries" "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/conf" "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/dsym" "${RELEASE_ROOT}/${RELEASE_NAME}"
|
||||
/usr/bin/tar -C "${RELEASE_ROOT}" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
|
||||
/bin/mkdir -p "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/binaries" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/conf" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/dsym" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/usr/bin/tar -C "${SCRATCH}/tar_root" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
|
||||
|
||||
echo "creating app pkg"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
|
||||
@@ -130,6 +131,7 @@ echo "creating app pkg"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.syncservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
|
||||
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"
|
||||
|
||||
@@ -26,6 +26,9 @@ mkdir -p /usr/local/bin
|
||||
# Load com.google.santa.metricservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
|
||||
# Load com.google.santa.syncservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
/bin/launchctl remove com.google.santad || true
|
||||
/bin/launchctl remove com.google.santa.bundleservice || true
|
||||
/bin/launchctl remove com.google.santa.metricservice || true
|
||||
/bin/launchctl remove com.google.santa.syncservice || true
|
||||
|
||||
/bin/sleep 1
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -27,7 +27,10 @@ fi
|
||||
# Unload metric service
|
||||
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
# Unload sync service
|
||||
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
@@ -59,6 +62,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
|
||||
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
@@ -74,6 +78,9 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
# Load com.google.santa.metricservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
|
||||
# Load com.google.santa.syncservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# remove helper XPC services
|
||||
/bin/launchctl remove com.google.santa.bundleservice
|
||||
/bin/launchctl remove com.google.santa.metricservice
|
||||
/bin/launchctl remove com.google.santa.syncservice
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
@@ -28,6 +29,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
|
||||
/bin/rm -f /usr/local/bin/santactl # just a symlink
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Santa [](https://github.com/google/santa/actions/workflows/ci.yml) [](https://coveralls.io/github/google/santa?branch=main)
|
||||
|
||||
<p align="center">
|
||||
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
<img src="https://raw.githubusercontent.com/google/santa/main/Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</p>
|
||||
|
||||
Santa is a binary authorization system for macOS. It consists of a system
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -119,9 +119,16 @@
|
||||
if (!formatStr.length) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_identifier%"
|
||||
withString:event.fileSHA256];
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%bundle_or_file_identifier%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
// response methods.
|
||||
typedef enum {
|
||||
ACTION_UNSET = 0,
|
||||
|
||||
@@ -74,15 +72,9 @@ typedef struct santa_vnode_id_t {
|
||||
bool operator==(const santa_vnode_id_t &rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
// This _must not_ be used for anything security-sensitive. It exists solely
|
||||
// to make the msleep/wakeup calls easier.
|
||||
uint64_t unsafe_simple_id() const {
|
||||
return (((uint64_t)fsid << 32) | fileid);
|
||||
}
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
// Message struct that is sent down the IODataQueue.
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
santa_vnode_id_t vnode_id;
|
||||
@@ -93,18 +85,17 @@ typedef struct {
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
char ttypath[MAXPATHLEN];
|
||||
// For file events, this is the process name.
|
||||
// For exec requests, this is the parent process name.
|
||||
// While process names can technically be 4*MAXPATHLEN, that never
|
||||
// actually happens, so only take MAXPATHLEN and throw away any excess.
|
||||
char pname[MAXPATHLEN];
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to a copy of
|
||||
// the message.
|
||||
// This points to a copy of the original ES message.
|
||||
void *es_message;
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to an
|
||||
// NSArray of the arguments.
|
||||
// This points to an NSArray of the process arguments.
|
||||
void *args_array;
|
||||
} santa_message_t;
|
||||
|
||||
|
||||
@@ -93,6 +93,22 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
// The return status of a sync.
|
||||
typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
|
||||
SNTSyncStatusTypeSuccess,
|
||||
SNTSyncStatusTypePreflightFailed,
|
||||
SNTSyncStatusTypeEventUploadFailed,
|
||||
SNTSyncStatusTypeRuleDownloadFailed,
|
||||
SNTSyncStatusTypePostflightFailed,
|
||||
SNTSyncStatusTypeTooManySyncsInProgress,
|
||||
SNTSyncStatusTypeMissingSyncBaseURL,
|
||||
SNTSyncStatusTypeMissingMachineID,
|
||||
SNTSyncStatusTypeDaemonTimeout,
|
||||
SNTSyncStatusTypeSyncStarted,
|
||||
SNTSyncStatusTypeUnknown,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -49,6 +49,9 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
/// Get the logging level for this process.
|
||||
LogLevel EffectiveLogLevel();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
@@ -32,6 +26,21 @@ void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
LogLevel EffectiveLogLevel() {
|
||||
#ifdef DEBUG
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if ([SNTConfigurator configurator].enableDebugLogging) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
});
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static NSString *binaryName;
|
||||
@@ -41,10 +50,6 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
dispatch_once(&pred, ^{
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
|
||||
if ([SNTConfigurator configurator].enableDebugLogging) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
|
||||
@@ -53,7 +58,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
}
|
||||
});
|
||||
|
||||
if (logLevel < level) return;
|
||||
if (EffectiveLogLevel() < level) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,18 +20,37 @@
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// A block that reports the number of rules processed.
|
||||
/// TODO(bur): Add more details about the sync.
|
||||
typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
|
||||
|
||||
///
|
||||
/// Protocol implemented by syncservice and utilized by daemon and ctl for communication with a
|
||||
/// sync server.
|
||||
///
|
||||
@protocol SNTSyncServiceXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
- (void)performFullSyncWithReply:(SNTFullSyncReplyBlock)reply;
|
||||
|
||||
// The syncservice regularly syncs with a configured sync server. Use this method to sync out of
|
||||
// band. The syncservice ensures syncs do not run concurrently.
|
||||
//
|
||||
// Pass an NSXPCListenerEndpoint whose associated NSXPCListener exports an object that implements
|
||||
// the SNTSyncServiceLogReceiverXPC protocol. The caller will receive sync logs over this listener.
|
||||
// This is required.
|
||||
//
|
||||
// Syncs are enqueued in order and executed serially. kMaxEnqueuedSyncs limits the number of syncs
|
||||
// in the queue. If the queue is full calls to this method will be dropped and
|
||||
// SNTSyncStatusTypeTooManySyncsInProgress will be passed into the reply block.
|
||||
//
|
||||
// Pass true to isClean to perform a clean sync, defaults to false.
|
||||
//
|
||||
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
|
||||
isClean:(BOOL)cleanSync
|
||||
reply:(void (^)(SNTSyncStatusType))reply;
|
||||
|
||||
// Spindown the syncservice. The syncservice will not automatically start back up.
|
||||
// A new connection to the syncservice will bring it back up. This allows us to avoid running
|
||||
// the syncservice needlessly when there is no configured sync server.
|
||||
- (void)spindown;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncServiceInterface : NSObject
|
||||
@@ -54,3 +73,11 @@ typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
///
|
||||
/// Protocol implemented by santactl sync and used to receive log messages from
|
||||
/// the syncservice during a user initiated sync.
|
||||
///
|
||||
@protocol SNTSyncServiceLogReceiverXPC
|
||||
- (void)didReceiveLog:(NSString *)log;
|
||||
@end
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by santactl and utilized by santad
|
||||
@protocol SNTSyncdXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncdInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)syncdInterface;
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCSyncdInterface
|
||||
|
||||
+ (NSXPCInterface *)syncdInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,12 +28,11 @@
|
||||
@protocol SNTUnprivilegedDaemonControlXPC
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
/// Cache Ops
|
||||
///
|
||||
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
|
||||
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
|
||||
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
|
||||
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,7 +17,7 @@
|
||||
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -15,7 +15,7 @@
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="515" height="318"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="515" height="318"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
return @"";
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -19,4 +19,5 @@
|
||||
|
||||
@interface SNTCommandMetrics : SNTCommand <SNTCommandProtocol>
|
||||
- (void)prettyPrintMetrics:(NSDictionary *)metircs asJSON:(BOOL)exportJSON;
|
||||
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args;
|
||||
@end
|
||||
|
||||
@@ -41,6 +41,7 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"Provides metrics about Santa's operation while it's running.\n"
|
||||
@"Pass prefixes to filter list of metrics, if desired.\n"
|
||||
@" Use --json to output in JSON format");
|
||||
}
|
||||
|
||||
@@ -122,6 +123,26 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
[self prettyPrintMetricValues:normalizedMetrics[@"metrics"]];
|
||||
}
|
||||
|
||||
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args {
|
||||
NSMutableDictionary *outer = [metrics mutableCopy];
|
||||
NSMutableDictionary *inner = [NSMutableDictionary dictionary];
|
||||
__block BOOL hadFilter = NO;
|
||||
|
||||
[metrics[@"metrics"] enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
||||
for (NSString *arg in args) {
|
||||
if ([arg hasPrefix:@"-"]) continue;
|
||||
|
||||
hadFilter = YES;
|
||||
if ([key hasPrefix:arg]) {
|
||||
inner[key] = value;
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
outer[@"metrics"] = inner;
|
||||
return hadFilter ? outer : metrics;
|
||||
}
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
__block NSDictionary *metrics;
|
||||
|
||||
@@ -138,6 +159,8 @@ REGISTER_COMMAND_NAME(@"metrics")
|
||||
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
|
||||
}
|
||||
|
||||
metrics = [self filterMetrics:metrics withArguments:arguments];
|
||||
|
||||
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -131,4 +131,26 @@
|
||||
@"Metrics command command did not produce expected output");
|
||||
}
|
||||
|
||||
- (void)testFiltering {
|
||||
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
|
||||
|
||||
NSDictionary *metricDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
|
||||
NSDictionary *filtered;
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[]];
|
||||
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"], @"No filtering with no args");
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json" ]];
|
||||
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"],
|
||||
@"No filtering with no metric args");
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json", @"/santa" ]];
|
||||
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 3,
|
||||
@"Expected filter of metrics with /santa to return 3 metrics");
|
||||
|
||||
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"/build", @"/santa" ]];
|
||||
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 4,
|
||||
@"Expected filter of metrics with /build and /santa to return 4 metrics");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -21,7 +21,8 @@ es_string_token_t MakeStringToken(const NSString *_Nonnull s);
|
||||
|
||||
es_file_t MakeESFile(const char *_Nonnull path);
|
||||
es_process_t MakeESProcess(es_file_t *_Nonnull esFile);
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *_Nonnull instigator, struct timespec ts);
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *_Nonnull instigator,
|
||||
struct timespec ts);
|
||||
CF_EXTERN_C_END
|
||||
|
||||
@class ESMessage;
|
||||
@@ -68,6 +69,21 @@ API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
|
||||
es_handler_block_t _Nonnull handler);
|
||||
|
||||
API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_return_t es_mute_process(es_client_t * _Nonnull client,
|
||||
const audit_token_t * _Nonnull audit_token);
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_return_t es_muted_paths_events(es_client_t *_Nonnull client,
|
||||
es_muted_paths_t *_Nonnull *_Nullable muted_paths);
|
||||
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
void es_release_muted_paths(es_muted_paths_t *_Nonnull muted_paths);
|
||||
#endif
|
||||
|
||||
API_AVAILABLE(macos(10.15))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_respond_result_t es_respond_auth_result(es_client_t *_Nonnull client,
|
||||
|
||||
@@ -45,7 +45,8 @@ es_process_t MakeESProcess(es_file_t *esFile) {
|
||||
return esProc;
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator, struct timespec ts) {
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator,
|
||||
struct timespec ts) {
|
||||
es_message_t esMsg = {};
|
||||
|
||||
esMsg.time = ts;
|
||||
@@ -205,6 +206,18 @@ CF_EXTERN_C_END
|
||||
[self.clients addObject:mockClient];
|
||||
}
|
||||
|
||||
- (BOOL)removeClient:(es_client_t *_Nonnull)client {
|
||||
MockESClient *clientToRemove = [self findClient:client];
|
||||
|
||||
if (!clientToRemove) {
|
||||
NSLog(@"Attempted to remove unknown mock es client.");
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.clients removeObject:clientToRemove];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)triggerHandler:(es_message_t *_Nonnull)msg {
|
||||
for (MockESClient *client in self.clients) {
|
||||
if (client.subscriptions[msg->event_type]) {
|
||||
@@ -233,17 +246,24 @@ CF_EXTERN_C_END
|
||||
return ES_RESPOND_RESULT_SUCCESS;
|
||||
};
|
||||
|
||||
- (MockESClient *)findClient:(es_client_t *)client {
|
||||
for (MockESClient *c in self.clients) {
|
||||
// Since we're mocking out a C interface and using this exact pointer as our
|
||||
// client identifier, only check for pointer equality.
|
||||
if (client == (__bridge es_client_t *)c) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setSubscriptions:(const es_event_type_t *_Nonnull)events
|
||||
event_count:(uint32_t)event_count
|
||||
value:(NSNumber *)value
|
||||
client:(es_client_t *)client {
|
||||
@synchronized(self) {
|
||||
MockESClient *toUpdate = nil;
|
||||
for (MockESClient *c in self.clients) {
|
||||
if (client == (__bridge es_client_t *)c) {
|
||||
toUpdate = c;
|
||||
}
|
||||
}
|
||||
MockESClient *toUpdate = [self findClient:client];
|
||||
|
||||
if (toUpdate == nil) {
|
||||
NSLog(@"setting subscription for unknown client");
|
||||
return;
|
||||
@@ -281,9 +301,36 @@ es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
|
||||
return ES_NEW_CLIENT_RESULT_SUCCESS;
|
||||
};
|
||||
|
||||
es_return_t es_mute_process(es_client_t * _Nonnull client,
|
||||
const audit_token_t * _Nonnull audit_token) {
|
||||
return ES_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
es_return_t es_muted_paths_events(es_client_t *_Nonnull client,
|
||||
es_muted_paths_t *_Nonnull *_Nullable muted_paths) {
|
||||
es_muted_paths_t *tmp = (es_muted_paths_t *)malloc(sizeof(es_muted_paths_t));
|
||||
|
||||
tmp->count = 0;
|
||||
*muted_paths = (es_muted_paths_t *_Nullable)tmp;
|
||||
|
||||
return ES_RETURN_SUCCESS;
|
||||
};
|
||||
|
||||
API_AVAILABLE(macos(12.0))
|
||||
API_UNAVAILABLE(ios, tvos, watchos)
|
||||
void es_release_muted_paths(es_muted_paths_t *_Nonnull muted_paths) {
|
||||
free(muted_paths);
|
||||
}
|
||||
#endif
|
||||
|
||||
API_AVAILABLE(macos(10.15))
|
||||
API_UNAVAILABLE(ios, tvos, watchos) es_return_t es_delete_client(es_client_t *_Nullable client) {
|
||||
[[MockEndpointSecurity mockEndpointSecurity] reset];
|
||||
if (![[MockEndpointSecurity mockEndpointSecurity] removeClient:client]) {
|
||||
return ES_RETURN_ERROR;
|
||||
}
|
||||
return ES_RETURN_SUCCESS;
|
||||
};
|
||||
|
||||
|
||||
@@ -29,21 +29,34 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
}
|
||||
|
||||
@implementation SNTCachingEndpointSecurityManager {
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *_decisionCache;
|
||||
// Create 2 separate caches, mapping from the (filesysem + vnode ID) to a decision with a timestamp.
|
||||
// The root cache is for decisions on the root volume, which can never be unmounted and the other
|
||||
// is for executions from all other volumes. This cache will be emptied if any volume is unmounted.
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *_rootDecisionCache;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *_nonRootDecisionCache;
|
||||
uint64_t _rootVnodeID;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// TODO(rah): Consider splitting into root/non-root cache
|
||||
_decisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
_rootDecisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
_nonRootDecisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
|
||||
// Store the filesystem ID of the root vnode for split-cache usage.
|
||||
// If the stat fails for any reason _rootVnodeID will be 0 and all decisions will be in a single cache.
|
||||
struct stat rootStat;
|
||||
if (stat("/", &rootStat) == 0) {
|
||||
_rootVnodeID = (uint64_t)rootStat.st_dev;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_decisionCache) delete _decisionCache;
|
||||
if (_rootDecisionCache) delete _rootDecisionCache;
|
||||
if (_nonRootDecisionCache) delete _nonRootDecisionCache;
|
||||
}
|
||||
|
||||
- (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
|
||||
@@ -120,6 +133,7 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
- (void)addToCache:(santa_vnode_id_t)identifier
|
||||
decision:(santa_action_t)decision
|
||||
currentTicks:(uint64_t)microsecs {
|
||||
auto _decisionCache = [self cacheForVnodeID:identifier];
|
||||
switch (decision) {
|
||||
case ACTION_REQUEST_BINARY:
|
||||
_decisionCache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
|
||||
@@ -150,25 +164,21 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
}
|
||||
|
||||
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly API_AVAILABLE(macos(10.15)) {
|
||||
_decisionCache->clear();
|
||||
_nonRootDecisionCache->clear();
|
||||
if (!nonRootOnly) _rootDecisionCache->clear();
|
||||
if (!self.connectionEstablished) return YES; // if not connected, there's nothing to flush.
|
||||
return es_clear_cache(self.client) == ES_CLEAR_CACHE_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheCounts {
|
||||
return @[ @(_decisionCache->count()), @(0) ];
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheBucketCount {
|
||||
// TODO: add this, maybe.
|
||||
return nil;
|
||||
return @[ @(_rootDecisionCache->count()), @(_nonRootDecisionCache->count()) ];
|
||||
}
|
||||
|
||||
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
|
||||
auto result = ACTION_UNSET;
|
||||
uint64_t decision_time = 0;
|
||||
|
||||
uint64_t cache_val = _decisionCache->get(vnodeID);
|
||||
uint64_t cache_val = [self cacheForVnodeID:vnodeID]->get(vnodeID);
|
||||
if (cache_val == 0) return result;
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
@@ -179,7 +189,7 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
auto expiry_time = decision_time + (500 * 100000); // kMaxCacheDenyTimeMilliseconds
|
||||
if (expiry_time < GetCurrentUptime()) {
|
||||
_decisionCache->remove(vnodeID);
|
||||
[self cacheForVnodeID:vnodeID]->remove(vnodeID);
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
}
|
||||
@@ -188,9 +198,13 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
|
||||
}
|
||||
|
||||
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeID {
|
||||
_decisionCache->remove(vnodeID);
|
||||
[self cacheForVnodeID:vnodeID]->remove(vnodeID);
|
||||
// TODO(rah): Look at a replacement for wakeup(), maybe NSCondition
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (SantaCache<santa_vnode_id_t, uint64_t> *)cacheForVnodeID:(santa_vnode_id_t)vnodeID {
|
||||
return (vnodeID.fsid == _rootVnodeID || _rootVnodeID == 0) ? _rootDecisionCache : _nonRootDecisionCache;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -170,6 +170,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
es_event_type_t events[] = {
|
||||
ES_EVENT_TYPE_AUTH_MOUNT,
|
||||
ES_EVENT_TYPE_AUTH_REMOUNT,
|
||||
};
|
||||
|
||||
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
|
||||
@@ -199,44 +200,77 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return;
|
||||
}
|
||||
|
||||
long mountMode = m->event.mount.statfs->f_flags;
|
||||
struct statfs *eventStatFS;
|
||||
BOOL isRemount = NO;
|
||||
|
||||
switch (m->event_type) {
|
||||
case ES_EVENT_TYPE_AUTH_MOUNT: eventStatFS = m->event.mount.statfs; break;
|
||||
case ES_EVENT_TYPE_AUTH_REMOUNT:
|
||||
eventStatFS = m->event.remount.statfs;
|
||||
isRemount = YES;
|
||||
break;
|
||||
default:
|
||||
LOGE(@"Unexpected Event Type passed to DeviceManager handleAuthMount: %d", m->event_type);
|
||||
// Fail closed.
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
assert(0 && "SNTDeviceManager: unexpected event type");
|
||||
return;
|
||||
}
|
||||
|
||||
long mountMode = eventStatFS->f_flags;
|
||||
pid_t pid = audit_token_to_pid(m->process->audit_token);
|
||||
LOGI(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
|
||||
LOGD(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
|
||||
m->process->executable->path.data, pid, mountMode);
|
||||
|
||||
DADiskRef disk =
|
||||
DADiskCreateFromBSDName(NULL, self.diskArbSession, m->event.mount.statfs->f_mntfromname);
|
||||
DADiskRef disk = DADiskCreateFromBSDName(NULL, self.diskArbSession, eventStatFS->f_mntfromname);
|
||||
CFAutorelease(disk);
|
||||
|
||||
// TODO(tnek): Log all of the other attributes available in diskInfo into a structured log format.
|
||||
NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk));
|
||||
BOOL isInternal = [diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceInternalKey] boolValue];
|
||||
BOOL isRemovable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaRemovableKey] boolValue];
|
||||
BOOL isUSB =
|
||||
[diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey] isEqualTo:@"USB"];
|
||||
BOOL isEjectable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaEjectableKey] boolValue];
|
||||
NSString *protocol = diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey];
|
||||
BOOL isUSB = [protocol isEqualToString:@"USB"];
|
||||
BOOL isVirtual = [protocol isEqualToString: @"Virtual Interface"];
|
||||
|
||||
if (!isRemovable || !isUSB) {
|
||||
NSString *kind = diskInfo[(__bridge NSString *)kDADiskDescriptionMediaKindKey];
|
||||
|
||||
// TODO: check kind and protocol for banned things (e.g. MTP).
|
||||
LOGD(@"SNTDeviceManager: DiskInfo Protocol: %@ Kind: %@ isInternal: %d isRemovable: %d "
|
||||
@"isEjectable: %d",
|
||||
protocol, kind, isInternal, isRemovable, isEjectable);
|
||||
|
||||
// If the device is internal or virtual we are okay with the operation. We
|
||||
// also are okay with operations for devices that are non-removal as long as
|
||||
// they are NOT a USB device.
|
||||
if (isInternal || isVirtual || (!isRemovable && !isEjectable && !isUSB)) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
return;
|
||||
}
|
||||
|
||||
SNTDeviceEvent *event = [[SNTDeviceEvent alloc]
|
||||
initWithOnName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntonname]
|
||||
fromName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntfromname]];
|
||||
initWithOnName:[NSString stringWithUTF8String:eventStatFS->f_mntonname]
|
||||
fromName:[NSString stringWithUTF8String:eventStatFS->f_mntfromname]];
|
||||
|
||||
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
|
||||
|
||||
if (shouldRemount) {
|
||||
event.remountArgs = self.remountArgs;
|
||||
long remountOpts = mountArgsToMask(self.remountArgs);
|
||||
if (mountMode & remountOpts) {
|
||||
|
||||
LOGD(@"SNTDeviceManager: mountMode: %@", maskToMountArgs(mountMode));
|
||||
LOGD(@"SNTDeviceManager: remountOpts: %@", maskToMountArgs(remountOpts));
|
||||
|
||||
if ((mountMode & remountOpts) == remountOpts && !isRemount) {
|
||||
LOGD(@"SNTDeviceManager: Allowing as mount as flags match remountOpts");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
return;
|
||||
}
|
||||
|
||||
long newMode = mountMode | remountOpts;
|
||||
LOGI(@"SNTDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)",
|
||||
m->event.mount.statfs->f_mntfromname, m->event.mount.statfs->f_mntonname, mountMode,
|
||||
newMode);
|
||||
eventStatFS->f_mntfromname, eventStatFS->f_mntonname, mountMode, newMode);
|
||||
[self remount:disk mountMode:newMode];
|
||||
}
|
||||
|
||||
@@ -270,26 +304,34 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// ES will kill our whole client if we don't meet the es_message auth deadline, so we try to
|
||||
// gracefully handle it with a deny-by-default in the worst-case before it can do that.
|
||||
// This isn't an issue for notify events, so we're in no rush for those.
|
||||
std::shared_ptr<std::atomic<bool>> responded;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
fclose(stdout);
|
||||
}
|
||||
|
||||
- (ESResponse *)triggerTestMount:(SNTDeviceManager *)deviceManager
|
||||
mockES:(MockEndpointSecurity *)mockES
|
||||
mockDA:(MockDiskArbitration *)mockDA {
|
||||
- (ESResponse *)triggerTestMountEvent:(SNTDeviceManager *)deviceManager
|
||||
mockES:(MockEndpointSecurity *)mockES
|
||||
mockDA:(MockDiskArbitration *)mockDA
|
||||
eventType:(es_event_type_t)eventType
|
||||
diskInfoOverrides:(NSDictionary *)diskInfo {
|
||||
if (!deviceManager.subscribed) {
|
||||
// [deviceManager listen] is synchronous, but we want to asynchronously dispatch it
|
||||
// with an enforced timeout to ensure that we never run into issues where the client
|
||||
@@ -84,26 +86,39 @@
|
||||
@"DAMediaBSDName" : test_mntfromname,
|
||||
};
|
||||
|
||||
if (diskInfo != nil) {
|
||||
NSMutableDictionary *mergedDiskDescription = [disk.diskDescription mutableCopy];
|
||||
for (NSString *key in diskInfo) {
|
||||
mergedDiskDescription[key] = diskInfo[key];
|
||||
}
|
||||
disk.diskDescription = (NSDictionary *)mergedDiskDescription;
|
||||
}
|
||||
|
||||
[mockDA insert:disk bsdName:test_mntfromname];
|
||||
|
||||
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
|
||||
m.binaryPath = @"/System/Library/Filesystems/msdos.fs/Contents/Resources/mount_msdos";
|
||||
m.message->action_type = ES_ACTION_TYPE_AUTH;
|
||||
m.message->event_type = ES_EVENT_TYPE_AUTH_MOUNT;
|
||||
m.message->event = (es_events_t){.mount = {.statfs = fs}};
|
||||
m.message->event_type = eventType;
|
||||
if (eventType == ES_EVENT_TYPE_AUTH_MOUNT) {
|
||||
m.message->event = (es_events_t){.mount = {.statfs = fs}};
|
||||
} else {
|
||||
m.message->event = (es_events_t){.remount = {.statfs = fs}};
|
||||
}
|
||||
}];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
|
||||
XCTestExpectation *mountExpectation =
|
||||
[self expectationWithDescription:@"Wait for response from ES"];
|
||||
__block ESResponse *got;
|
||||
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
[mockES registerResponseCallback:eventType
|
||||
withCallback:^(ESResponse *r) {
|
||||
got = r;
|
||||
[expectation fulfill];
|
||||
[mountExpectation fulfill];
|
||||
}];
|
||||
|
||||
[mockES triggerHandler:m.message];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:60.0];
|
||||
[self waitForExpectations:@[ mountExpectation ] timeout:60.0];
|
||||
free(fs);
|
||||
|
||||
return got;
|
||||
@@ -118,7 +133,12 @@
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = NO;
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
|
||||
}
|
||||
|
||||
@@ -145,7 +165,11 @@
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, YES);
|
||||
@@ -179,7 +203,11 @@
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
|
||||
@@ -190,4 +218,74 @@
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testEnsureRemountsCannotChangePerms {
|
||||
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
|
||||
[mockES reset];
|
||||
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
[mockDA reset];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
|
||||
|
||||
__block NSString *gotmntonname, *gotmntfromname;
|
||||
__block NSArray<NSString *> *gotRemountedArgs;
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
gotRemountedArgs = event.remountArgs;
|
||||
gotmntonname = event.mntonname;
|
||||
gotmntfromname = event.mntfromname;
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_REMOUNT
|
||||
diskInfoOverrides:nil];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, YES);
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:10.0];
|
||||
|
||||
XCTAssertEqualObjects(gotRemountedArgs, deviceManager.remountArgs);
|
||||
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testEnsureDMGsDoNotPrompt {
|
||||
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
|
||||
[mockES reset];
|
||||
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
[mockDA reset];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
XCTFail(@"Should not be called");
|
||||
};
|
||||
|
||||
NSDictionary *diskInfo = @{
|
||||
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey: @"Virtual Interface",
|
||||
(__bridge NSString *)kDADiskDescriptionDeviceModelKey: @"Disk Image",
|
||||
(__bridge NSString *)kDADiskDescriptionMediaNameKey: @"disk image",
|
||||
};
|
||||
|
||||
|
||||
ESResponse *got = [self triggerTestMountEvent:deviceManager
|
||||
mockES:mockES
|
||||
mockDA:mockDA
|
||||
eventType:ES_EVENT_TYPE_AUTH_MOUNT
|
||||
diskInfoOverrides:diskInfo];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
|
||||
XCTAssertEqual(mockDA.wasRemounted, NO);
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -34,7 +34,6 @@ static const pid_t PID_MAX = 99999;
|
||||
@property(nonatomic) SNTPrefixTree *prefixTree;
|
||||
@property(nonatomic, readonly) dispatch_queue_t esAuthQueue;
|
||||
@property(nonatomic, readonly) dispatch_queue_t esNotifyQueue;
|
||||
@property(nonatomic, readonly) pid_t selfPID;
|
||||
|
||||
@end
|
||||
|
||||
@@ -45,11 +44,10 @@ static const pid_t PID_MAX = 99999;
|
||||
if (self) {
|
||||
// To avoid nil deref from es_events arriving before listenForDecisionRequests or
|
||||
// listenForLogRequests in the MockEndpointSecurity testing util.
|
||||
_decisionCallback = ^(santa_message_t) {
|
||||
};
|
||||
_logCallback = ^(santa_message_t) {
|
||||
};
|
||||
_decisionCallback = ^(santa_message_t) {};
|
||||
_logCallback = ^(santa_message_t) {};
|
||||
[self establishClient];
|
||||
[self muteSelf];
|
||||
_prefixTree = new SNTPrefixTree();
|
||||
_esAuthQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.es_auth", DISPATCH_QUEUE_CONCURRENT);
|
||||
@@ -58,7 +56,6 @@ static const pid_t PID_MAX = 99999;
|
||||
_esNotifyQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.es_notify", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(_esNotifyQueue, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
|
||||
_selfPID = getpid();
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -72,6 +69,24 @@ static const pid_t PID_MAX = 99999;
|
||||
if (_prefixTree) delete _prefixTree;
|
||||
}
|
||||
|
||||
- (void)muteSelf {
|
||||
audit_token_t myAuditToken;
|
||||
mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT;
|
||||
if (task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)&myAuditToken, &count) ==
|
||||
KERN_SUCCESS) {
|
||||
if (es_mute_process(self.client, &myAuditToken) == ES_RETURN_SUCCESS) {
|
||||
return;
|
||||
} else {
|
||||
LOGE(@"Failed to mute this client's process, its events will not be muted.");
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Failed to fetch this client's audit token. Its events will not be muted.");
|
||||
}
|
||||
|
||||
// If we get here, Santa was unable to mute itself. Assume transitory and bail.
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
- (void)establishClient API_AVAILABLE(macos(10.15)) {
|
||||
while (!self.client) {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
@@ -86,9 +101,7 @@ static const pid_t PID_MAX = 99999;
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
}
|
||||
if (self.selfPID != pid) {
|
||||
LOGD(@"Skipping event type: 0x%x from es_client pid: %d", m->event_type, pid);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,9 +127,10 @@ static const pid_t PID_MAX = 99999;
|
||||
// Create a transitive rule if the file was modified by a running compiler
|
||||
if ([self isCompilerPID:pid]) {
|
||||
santa_message_t sm = {};
|
||||
BOOL truncated = [SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)];
|
||||
BOOL truncated =
|
||||
[SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)];
|
||||
if (truncated) {
|
||||
LOGE(@"CLOSE: error creating transitive rule, the path is truncated: path=%s pid=%d",
|
||||
sm.path, pid);
|
||||
@@ -182,7 +196,12 @@ static const pid_t PID_MAX = 99999;
|
||||
case ES_EVENT_TYPE_NOTIFY_UNMOUNT: {
|
||||
// Flush the non-root cache - the root disk cannot be unmounted
|
||||
// so it isn't necessary to flush its cache.
|
||||
[self flushCacheNonRootOnly:YES];
|
||||
//
|
||||
// Flushing the cache calls back into ES. We need to perform this off the handler thread
|
||||
// otherwise we could potentially deadlock.
|
||||
dispatch_async(self.esAuthQueue, ^() {
|
||||
[self flushCacheNonRootOnly:YES];
|
||||
});
|
||||
|
||||
// Skip all other processing
|
||||
return;
|
||||
@@ -210,30 +229,42 @@ static const pid_t PID_MAX = 99999;
|
||||
|
||||
switch (m->action_type) {
|
||||
case ES_ACTION_TYPE_AUTH: {
|
||||
// Copy the message
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
|
||||
dispatch_semaphore_t processingSema = dispatch_semaphore_create(0);
|
||||
// Add 1 to the processing semaphore. We're not creating it with a starting
|
||||
// value of 1 because that requires that the semaphore is not deallocated
|
||||
// until its value matches the starting value, which we don't need.
|
||||
dispatch_semaphore_signal(processingSema);
|
||||
dispatch_semaphore_t deadlineExpiredSema = dispatch_semaphore_create(0);
|
||||
|
||||
// Create a timer to deny the execution 5 seconds before the deadline,
|
||||
// if a response hasn't already been sent. This block will still be enqueued if
|
||||
// the the deadline - 5 secs is < DISPATCH_TIME_NOW.
|
||||
// As of 10.15.5, a typical deadline is 60 seconds.
|
||||
auto responded = std::make_shared<std::atomic<bool>>(false);
|
||||
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -5), self.esAuthQueue, ^(void) {
|
||||
if (responded->load()) return;
|
||||
LOGE(@"Deadline reached: deny pid=%d ret=%d", pid,
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false));
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Handler has already responded, nothing to do.
|
||||
return;
|
||||
}
|
||||
LOGE(@"SNTEndpointSecurityManager: deadline reached: deny pid=%d ret=%d", pid,
|
||||
es_respond_auth_result(self.client, mc, ES_AUTH_RESULT_DENY, false));
|
||||
dispatch_semaphore_signal(deadlineExpiredSema);
|
||||
});
|
||||
|
||||
// Copy the message and return control back to ES
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
// Dispatch off to the handler and return control to ES.
|
||||
dispatch_async(self.esAuthQueue, ^{
|
||||
[self messageHandler:mc];
|
||||
responded->store(true);
|
||||
if (dispatch_semaphore_wait(processingSema, DISPATCH_TIME_NOW) != 0) {
|
||||
// Deadline expired, wait for deadline block to finish.
|
||||
dispatch_semaphore_wait(deadlineExpiredSema, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
es_free_message(mc);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ES_ACTION_TYPE_NOTIFY: {
|
||||
// Don't log fileop events from com.google.santa.daemon
|
||||
if (self.selfPID == pid && m->event_type != ES_EVENT_TYPE_NOTIFY_EXEC) return;
|
||||
|
||||
// Copy the message and return control back to ES
|
||||
es_message_t *mc = es_copy_message(m);
|
||||
dispatch_async(self.esNotifyQueue, ^{
|
||||
@@ -288,6 +319,10 @@ static const pid_t PID_MAX = 99999;
|
||||
targetFile = m->event.exec.target->executable;
|
||||
targetProcess = m->event.exec.target;
|
||||
callback = self.decisionCallback;
|
||||
|
||||
[SNTEndpointSecurityManager populateBufferFromESFile:m->process->tty
|
||||
buffer:sm.ttypath
|
||||
size:sizeof(sm.ttypath)];
|
||||
break;
|
||||
}
|
||||
case ES_EVENT_TYPE_NOTIFY_EXEC: {
|
||||
@@ -314,8 +349,7 @@ static const pid_t PID_MAX = 99999;
|
||||
NSString *path = [[NSString alloc] initWithBytes:pathToken.data
|
||||
length:pathToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if ([self isDatabasePath:path] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:path]) {
|
||||
LOGW(@"Preventing attempt to delete Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -329,8 +363,7 @@ static const pid_t PID_MAX = 99999;
|
||||
length:pathToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
if ([self isDatabasePath:path] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:path]) {
|
||||
LOGW(@"Preventing attempt to rename Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -340,8 +373,7 @@ static const pid_t PID_MAX = 99999;
|
||||
NSString *destPath = [[NSString alloc] initWithBytes:destToken.data
|
||||
length:destToken.length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if ([self isDatabasePath:destPath] &&
|
||||
audit_token_to_pid(m->process->audit_token) != self.selfPID) {
|
||||
if ([self isDatabasePath:destPath]) {
|
||||
LOGW(@"Preventing attempt to overwrite Santa databases!");
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true);
|
||||
return;
|
||||
@@ -544,6 +576,7 @@ static const pid_t PID_MAX = 99999;
|
||||
// Returns YES if the path was truncated.
|
||||
// The populated buffer will be NUL terminated.
|
||||
+ (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
|
||||
if (file == NULL) return NO;
|
||||
return [SNTEndpointSecurityManager populateBufferFromString:file->path.data
|
||||
buffer:buffer
|
||||
size:size];
|
||||
|
||||
@@ -447,7 +447,11 @@
|
||||
logger = [[SNTProtobufEventLog alloc] init];
|
||||
break;
|
||||
}
|
||||
default: logger = nil;
|
||||
case SNTEventLogTypeNull: {
|
||||
// Messages sent to nil objects do nothing, which is perfect for a null logger.
|
||||
logger = nil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return logger;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -21,13 +21,9 @@
|
||||
|
||||
@interface SNTSyncdQueue : NSObject
|
||||
|
||||
@property(nonatomic) MOLXPCConnection *syncdConnection;
|
||||
@property(copy) void (^invalidationHandler)(void);
|
||||
@property(copy) void (^acceptedHandler)(void);
|
||||
@property(nonatomic) MOLXPCConnection *syncConnection;
|
||||
|
||||
- (void)addEvents:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
|
||||
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)startSyncingEvents;
|
||||
- (void)stopSyncingEvents;
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,12 +18,11 @@
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
#import "Source/common/SNTXPCSyncServiceInterface.h"
|
||||
|
||||
@interface SNTSyncdQueue ()
|
||||
@property NSCache<NSString *, NSDate *> *uploadBackoff;
|
||||
@property dispatch_queue_t syncdQueue;
|
||||
@property dispatch_semaphore_t sema;
|
||||
@end
|
||||
|
||||
@implementation SNTSyncdQueue
|
||||
@@ -34,7 +33,6 @@
|
||||
_uploadBackoff = [[NSCache alloc] init];
|
||||
_uploadBackoff.countLimit = 128;
|
||||
_syncdQueue = dispatch_queue_create("com.google.syncd_queue", DISPATCH_QUEUE_SERIAL);
|
||||
_sema = dispatch_semaphore_create(0);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -45,15 +43,14 @@
|
||||
NSString *hash = isFromBundle ? first.fileBundleHash : first.fileSHA256;
|
||||
if (![self backoffForPrimaryHash:hash]) return;
|
||||
[self dispatchBlockOnSyncdQueue:^{
|
||||
[self.syncdConnection.remoteObjectProxy postEventsToSyncServer:events
|
||||
isFromBundle:isFromBundle];
|
||||
[self.syncConnection.remoteObjectProxy postEventsToSyncServer:events fromBundle:isFromBundle];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply {
|
||||
if (![self backoffForPrimaryHash:event.fileBundleHash]) return;
|
||||
[self dispatchBlockOnSyncdQueue:^{
|
||||
[self.syncdConnection.remoteObjectProxy
|
||||
[self.syncConnection.remoteObjectProxy
|
||||
postBundleEventToSyncServer:event
|
||||
reply:^(SNTBundleEventAction action) {
|
||||
// Remove the backoff entry for the initial block event. The same
|
||||
@@ -67,25 +64,10 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)startSyncingEvents {
|
||||
dispatch_semaphore_signal(self.sema);
|
||||
}
|
||||
|
||||
- (void)stopSyncingEvents {
|
||||
self.sema = dispatch_semaphore_create(0);
|
||||
}
|
||||
|
||||
// Hold events for a few seconds to allow santad and santactl to establish connections.
|
||||
// If the connections are not established in time drop the event from the queue.
|
||||
// They will be uploaded during a full sync.
|
||||
- (void)dispatchBlockOnSyncdQueue:(void (^)(void))block {
|
||||
if (!block) return;
|
||||
dispatch_async(self.syncdQueue, ^{
|
||||
if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
|
||||
if (block) block();
|
||||
dispatch_semaphore_signal(self.sema);
|
||||
} else {
|
||||
LOGD(@"Dropping block %@ from com.google.syncd_queue", block);
|
||||
}
|
||||
block();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
37
Source/santasyncservice/SNTPushNotifications.h
Normal file
37
Source/santasyncservice/SNTPushNotifications.h
Normal 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
|
||||
189
Source/santasyncservice/SNTPushNotifications.m
Normal file
189
Source/santasyncservice/SNTPushNotifications.m
Normal 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
|
||||
31
Source/santasyncservice/SNTPushNotificationsTracker.h
Normal file
31
Source/santasyncservice/SNTPushNotificationsTracker.h
Normal 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
|
||||
99
Source/santasyncservice/SNTPushNotificationsTracker.m
Normal file
99
Source/santasyncservice/SNTPushNotificationsTracker.m
Normal 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
|
||||
33
Source/santasyncservice/SNTSyncBroadcaster.h
Normal file
33
Source/santasyncservice/SNTSyncBroadcaster.h
Normal 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
|
||||
73
Source/santasyncservice/SNTSyncBroadcaster.m
Normal file
73
Source/santasyncservice/SNTSyncBroadcaster.m
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
49
Source/santasyncservice/SNTSyncLogging.h
Normal file
49
Source/santasyncservice/SNTSyncLogging.h
Normal 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)
|
||||
32
Source/santasyncservice/SNTSyncLogging.m
Normal file
32
Source/santasyncservice/SNTSyncLogging.m
Normal 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];
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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 $?
|
||||
@@ -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 $?
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 $?
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -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. |
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
15
profiles/BUILD
Normal 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"],
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
"""The version for all Santa components."""
|
||||
|
||||
SANTA_VERSION = "2022.3"
|
||||
Reference in New Issue
Block a user