mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16f74cb85c | ||
|
|
aadc961429 | ||
|
|
be66fd92f4 | ||
|
|
feea349f25 | ||
|
|
1c04c3a257 | ||
|
|
818d3f645f | ||
|
|
15d6bb1f14 | ||
|
|
211dbd123f | ||
|
|
c67364fe76 | ||
|
|
2043983f69 | ||
|
|
2f408936a0 | ||
|
|
02c1d0f267 | ||
|
|
4728c346cc | ||
|
|
9588dd8a0e | ||
|
|
e3e48aed1b | ||
|
|
e60f9cf6c5 | ||
|
|
c7e309ccb1 | ||
|
|
ad8aafbd07 | ||
|
|
9e671c3dee | ||
|
|
d97abe36f2 | ||
|
|
faa8946056 | ||
|
|
8b2b1f0bfc | ||
|
|
16678cd5a0 | ||
|
|
0bd6a199a3 | ||
|
|
58e2b7e1b8 |
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
|
||||
build_driver: ${{ steps.step1.outputs.build_driver }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -26,16 +25,11 @@ jobs:
|
||||
|
||||
cat files.txt
|
||||
|
||||
build_driver=0
|
||||
build_and_run_tests=0
|
||||
|
||||
for file in `cat files.txt`; do
|
||||
if [[ $file = Source/* ]]; then
|
||||
build_and_run_test=1;
|
||||
if [[ $file = Source/santa_driver/* || $file = Source/common/* ]]; then
|
||||
build_driver=1;
|
||||
break;
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -46,13 +40,6 @@ jobs:
|
||||
echo "::set-output name=run_build_and_tests::false"
|
||||
fi
|
||||
|
||||
if [[ $build_driver != 0 ]]; then
|
||||
echo "NEED TO BUILD DRIVER"
|
||||
echo "::set-output name=build_driver::true"
|
||||
else
|
||||
echo "::set-output name=build_driver::false"
|
||||
fi
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [preqs]
|
||||
@@ -75,19 +62,6 @@ jobs:
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
|
||||
|
||||
build_driver:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.build_driver == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Driver
|
||||
run: bazel build --apple_generate_dsym -c opt :release_driver --define=SANTA_BUILD_TYPE=ci
|
||||
|
||||
unit_tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
60
BUILD
60
BUILD
@@ -11,7 +11,12 @@ exports_files(["LICENSE"])
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_version = SANTA_VERSION,
|
||||
build_label_pattern = "{build}",
|
||||
build_version = SANTA_VERSION + ".{build}",
|
||||
capture_groups = {
|
||||
"build": "\\d+",
|
||||
},
|
||||
fallback_build_label = "1",
|
||||
short_version_string = SANTA_VERSION,
|
||||
)
|
||||
|
||||
@@ -49,7 +54,6 @@ run_command(
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
|
||||
sudo kextunload -b com.google.santa-driver 2>/dev/null
|
||||
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
|
||||
""",
|
||||
)
|
||||
@@ -68,14 +72,11 @@ run_command(
|
||||
name = "reload",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/santa_driver",
|
||||
],
|
||||
cmd = """
|
||||
set -e
|
||||
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa/Santa.zip >/dev/null
|
||||
echo "You may be asked for your password for sudo"
|
||||
@@ -178,55 +179,6 @@ genrule(
|
||||
heuristic_label_expansion = 0,
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "release_driver",
|
||||
srcs = [
|
||||
"//Source/santa_driver",
|
||||
],
|
||||
outs = ["santa-driver-" + SANTA_VERSION + ".tar.gz"],
|
||||
cmd = select({
|
||||
"//conditions:default": """
|
||||
echo "ERROR: Trying to create a release tarball without optimization."
|
||||
echo "Please add '-c opt' flag to bazel invocation"
|
||||
""",
|
||||
":opt_build": """
|
||||
# Extract santa_driver.zip
|
||||
for SRC in $(SRCS); do
|
||||
if [ "$$(basename $${SRC})" == "santa_driver.zip" ]; then
|
||||
mkdir -p $(@D)/binaries
|
||||
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# Gather together the dSYMs. Throw an error if no dSYMs were found
|
||||
for SRC in $(SRCS); do
|
||||
case $${SRC} in
|
||||
*santa-driver.kext.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santa-driver.kext.dSYM
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Cause a build failure if the dSYMs are missing.
|
||||
if [[ ! -d "$(@D)/dsym" ]]; then
|
||||
echo "dsym dir missing: Did you forget to use --apple_generate_dsym?"
|
||||
echo "This flag is required for the 'release' target."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update all the timestamps to now. Bazel avoids timestamps to allow
|
||||
# builds to be hermetic and cacheable but for releases we want the
|
||||
# timestamps to be more-or-less correct.
|
||||
find $(@D)/{binaries,dsym} -exec touch {} \\;
|
||||
|
||||
# Create final output tar
|
||||
tar -C $(@D) -czpf $(@) binaries dsym
|
||||
""",
|
||||
}),
|
||||
heuristic_label_expansion = 0,
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>Santa</title>
|
||||
<options customize="never" allow-external-scripts="no"/>
|
||||
<options customize="never" allow-external-scripts="no" hostArchitectures="x86_64,arm64" />
|
||||
|
||||
<choices-outline>
|
||||
<line choice="default" />
|
||||
@@ -9,10 +9,8 @@
|
||||
|
||||
<choice id="default">
|
||||
<pkg-ref id="com.google.santa"/>
|
||||
<pkg-ref id="com.google.santa-driver"/>
|
||||
</choice>
|
||||
|
||||
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
|
||||
<pkg-ref id="com.google.santa-driver" active="system.compareVersions(my.target.systemVersion.ProductVersion, '10.15') < 0">kext.pkg</pkg-ref>
|
||||
|
||||
</installer-gui-script>
|
||||
|
||||
@@ -44,29 +44,27 @@ function die {
|
||||
}
|
||||
|
||||
readonly INPUT_APP="${RELEASE_ROOT}/binaries/Santa.app"
|
||||
readonly INPUT_KEXT="${RELEASE_ROOT}/binaries/santa-driver.kext"
|
||||
readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension"
|
||||
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
|
||||
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
|
||||
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
|
||||
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleVersion)"
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
|
||||
|
||||
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
|
||||
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
|
||||
readonly KEXT_PKG_ROOT="${SCRATCH}/kext_pkg_root"
|
||||
readonly APP_PKG_SCRIPTS="${SCRATCH}/pkg_scripts"
|
||||
readonly ENTITLEMENTS="${SCRATCH}/entitlements"
|
||||
|
||||
readonly SCRIPT_PATH="$(/usr/bin/dirname -- ${BASH_SOURCE[0]})"
|
||||
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${KEXT_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
|
||||
|
||||
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
|
||||
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
EN="${ENTITLEMENTS}/${BN}.entitlements"
|
||||
|
||||
@@ -85,7 +83,7 @@ for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INP
|
||||
done
|
||||
|
||||
# Notarize all the bundles
|
||||
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
|
||||
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "zipping ${BN}"
|
||||
@@ -96,8 +94,8 @@ for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
|
||||
done
|
||||
|
||||
# Staple the App and Kext.
|
||||
for ARTIFACT in "${INPUT_APP}" "${INPUT_KEXT}"; do
|
||||
# Staple the App.
|
||||
for ARTIFACT in "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "stapling ${BN}"
|
||||
@@ -153,22 +151,6 @@ echo "creating app pkg"
|
||||
--scripts "${APP_PKG_SCRIPTS}" \
|
||||
"${SCRATCH}/app.pkg"
|
||||
|
||||
echo "creating kext pkg"
|
||||
/bin/mkdir -p "${KEXT_PKG_ROOT}/Library/Extensions"
|
||||
/bin/cp -vXR "${RELEASE_ROOT}/binaries/santa-driver.kext" "${KEXT_PKG_ROOT}/Library/Extensions/"
|
||||
/usr/bin/pkgbuild --analyze --root "${KEXT_PKG_ROOT}" "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsRelocatable -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsVersionChecked -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleOverwriteAction -string upgrade "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace ChildBundles -json "[]" "${SCRATCH}/component.plist"
|
||||
|
||||
# Build kext package
|
||||
/usr/bin/pkgbuild --identifier "com.google.santa-driver" \
|
||||
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
|
||||
--root "${KEXT_PKG_ROOT}" \
|
||||
--component-plist "${SCRATCH}/component.plist" \
|
||||
"${SCRATCH}/kext.pkg"
|
||||
|
||||
# Build signed distribution package
|
||||
echo "productbuild pkg"
|
||||
/bin/mkdir -p "${SCRATCH}/${RELEASE_NAME}"
|
||||
|
||||
@@ -14,7 +14,6 @@ mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
# Remove the kext before com.google.santa.daemon loads if the SystemExtension is already present.
|
||||
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && rm -rf /Library/Extensions/santa-driver.kext
|
||||
|
||||
# Load com.google.santa.daemon, its main has logic to handle loading the kext
|
||||
|
||||
@@ -27,7 +27,7 @@ fi
|
||||
# Unload metric service
|
||||
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
@@ -53,10 +53,6 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
/bin/cp -r ${BINARIES}/Santa.app /Applications
|
||||
|
||||
# Only copy the kext if the SystemExtension is not present.
|
||||
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 || /bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions && /usr/sbin/kextcache -update-volume / -bundle-id com.google.santa-driver
|
||||
|
||||
/bin/mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && /Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
# remove helper XPC services
|
||||
/bin/launchctl remove com.google.santa.bundleservice
|
||||
/bin/launchctl remove com.google.santa.metricservice
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
11
README.md
11
README.md
@@ -4,12 +4,11 @@
|
||||
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</p>
|
||||
|
||||
Santa is a binary authorization system for macOS. It consists of a system or
|
||||
kernel extension (depending on the macOS version) that monitors for executions,
|
||||
a daemon that makes execution decisions based on the contents of a local
|
||||
database, a GUI agent that notifies the user in case of a block decision
|
||||
and a command-line utility for managing the system and synchronizing the
|
||||
database with a server.
|
||||
Santa is a binary authorization system for macOS. It consists of a system
|
||||
extension that monitors for executions, a daemon that makes execution decisions
|
||||
based on the contents of a local database, a GUI agent that notifies the user in
|
||||
case of a block decision and a command-line utility for managing the system and
|
||||
synchronizing the database with a server.
|
||||
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
proto_library(
|
||||
name = "log_proto",
|
||||
srcs = ["santa.proto"],
|
||||
deps = [
|
||||
"@com_google_protobuf//:any_proto",
|
||||
"@com_google_protobuf//:timestamp_proto",
|
||||
],
|
||||
)
|
||||
|
||||
objc_proto_library(
|
||||
name = "log_objc_proto",
|
||||
copts = ["-fno-objc-arc"],
|
||||
non_arc_srcs = ["Santa.pbobjc.m"],
|
||||
protos = [":log_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
features = ["layering_check"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
@@ -16,7 +37,7 @@ santa_unit_test(
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -30,6 +51,15 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage_SantaGUI",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
@@ -37,6 +67,7 @@ objc_library(
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTDeviceEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
@@ -47,11 +78,17 @@ objc_library(
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTAllowlistInfo",
|
||||
srcs = ["SNTAllowlistInfo.m"],
|
||||
hdrs = ["SNTAllowlistInfo.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
@@ -85,26 +122,13 @@ objc_library(
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTKernelCommon",
|
||||
hdrs = ["SNTKernelCommon.h"],
|
||||
name = "SNTCommon",
|
||||
hdrs = ["SNTCommon.h"],
|
||||
defines = [
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTLoggingKernel",
|
||||
hdrs = ["SNTLogging.h"],
|
||||
copts = [
|
||||
"-mkernel",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = [
|
||||
"KERNEL",
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -119,26 +143,10 @@ cc_library(
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = ["-std=c++11"],
|
||||
features = ["layering_check"],
|
||||
deps = [":SNTLogging"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTPrefixTreeKernel",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = [
|
||||
"-std=c++11",
|
||||
"-mkernel",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = [
|
||||
"KERNEL",
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
deps = [":SNTLoggingKernel"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
@@ -159,6 +167,7 @@ objc_library(
|
||||
cc_library(
|
||||
name = "SNTStrengthify",
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -241,8 +250,8 @@ objc_library(
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
|
||||
32
Source/common/SNTAllowlistInfo.h
Normal file
32
Source/common/SNTAllowlistInfo.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// Store information about new allowlist rules for later logging.
|
||||
///
|
||||
@interface SNTAllowlistInfo : NSObject
|
||||
|
||||
@property pid_t pid;
|
||||
@property int pidversion;
|
||||
@property NSString *targetPath;
|
||||
@property NSString *sha256;
|
||||
|
||||
- (instancetype)initWithPid:(pid_t)pid
|
||||
pidversion:(int)pidver
|
||||
targetPath:(NSString*)targetPath
|
||||
sha256:(NSString*)hash;
|
||||
|
||||
@end
|
||||
32
Source/common/SNTAllowlistInfo.m
Normal file
32
Source/common/SNTAllowlistInfo.m
Normal file
@@ -0,0 +1,32 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTAllowlistInfo.h"
|
||||
|
||||
@implementation SNTAllowlistInfo
|
||||
|
||||
- (instancetype)initWithPid:(pid_t)pid
|
||||
pidversion:(int)pidver
|
||||
targetPath:(NSString *)targetPath
|
||||
sha256:(NSString *)hash {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_pid = pid;
|
||||
_pidversion = pidver;
|
||||
_targetPath = targetPath;
|
||||
_sha256 = hash;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -24,12 +24,17 @@
|
||||
|
||||
///
|
||||
/// Return a message suitable for presenting to the user.
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one.
|
||||
///
|
||||
/// In SantaGUI this will return an NSAttributedString with links and formatting included
|
||||
/// while for santad all HTML will be properly stripped.
|
||||
///
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message;
|
||||
|
||||
///
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one, formatted using
|
||||
/// +[SNTBlockMessage formatMessage].
|
||||
///
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage;
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message {
|
||||
NSString *htmlHeader =
|
||||
@"<html><head><style>"
|
||||
@"body {"
|
||||
@@ -48,6 +47,22 @@
|
||||
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *message;
|
||||
if (customMessage.length) {
|
||||
message = customMessage;
|
||||
@@ -64,19 +79,7 @@
|
||||
@"because it has been deemed malicious.";
|
||||
}
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
return [SNTBlockMessage formatMessage:message];
|
||||
}
|
||||
|
||||
+ (NSString *)stringFromHTML:(NSString *)html {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
|
||||
@class MOLCertificate;
|
||||
|
||||
|
||||
@@ -13,48 +13,19 @@
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Common defines between kernel <-> userspace
|
||||
/// Common defines between daemon <-> client
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__KERNELCOMMON_H
|
||||
#define SANTA__COMMON__KERNELCOMMON_H
|
||||
#ifndef SANTA__COMMON__COMMON_H
|
||||
#define SANTA__COMMON__COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
// Defines the name of the userclient class and the driver bundle ID.
|
||||
#define USERCLIENT_CLASS "com_google_SantaDriver"
|
||||
#define USERCLIENT_ID "com.google.santa-driver"
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
kSantaUserClientAllowBinary,
|
||||
kSantaUserClientAllowCompiler,
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientAcknowledgeBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientRemoveCacheEntry,
|
||||
kSantaUserClientCacheCount,
|
||||
kSantaUserClientCheckCache,
|
||||
kSantaUserClientCacheBucketCount,
|
||||
kSantaUserClientFilemodPrefixFilterAdd,
|
||||
kSantaUserClientFilemodPrefixFilterReset,
|
||||
|
||||
// Any methods supported by the driver should be added above this line to
|
||||
// ensure this remains the count of methods.
|
||||
kSantaUserClientNMethods,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
QUEUETYPE_DECISION,
|
||||
QUEUETYPE_LOG,
|
||||
} santa_queuetype_t;
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
// response methods.
|
||||
typedef enum {
|
||||
@@ -137,10 +108,4 @@ typedef struct {
|
||||
void *args_array;
|
||||
} santa_message_t;
|
||||
|
||||
// Used for the kSantaUserClientCacheBucketCount request.
|
||||
typedef struct {
|
||||
uint16_t per_bucket[1024];
|
||||
uint64_t start;
|
||||
} santa_bucket_count_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
#endif // SANTA__COMMON__COMMON_H
|
||||
@@ -92,6 +92,7 @@ typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
|
||||
typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
@@ -100,7 +101,6 @@ typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
SNTMetricFormatTypeMonarchJSON,
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath =
|
||||
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
|
||||
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// The operating mode.
|
||||
/// The operating mode. Defaults to MONITOR.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTClientMode clientMode;
|
||||
|
||||
@@ -35,6 +35,17 @@
|
||||
///
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
|
||||
|
||||
///
|
||||
/// Enable Fail Close mode. Defaults to NO.
|
||||
/// This controls Santa's behavior when a failure occurs, such as an
|
||||
/// inability to read a file. By default, to prevent bugs or misconfiguration
|
||||
/// from rendering a machine inoperable Santa will fail open and allow
|
||||
/// execution. With this setting enabled, Santa will fail closed if the client
|
||||
/// is in LOCKDOWN mode, offering a higher level of security but with a higher
|
||||
/// potential for causing problems.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL failClosed;
|
||||
|
||||
///
|
||||
/// The regex of allowed paths. Regexes are specified in ICU format.
|
||||
///
|
||||
@@ -142,6 +153,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
|
||||
/// mailDirectory to specify a path. Use mailDirectoryFileSizeThresholdKB,
|
||||
/// mailDirectorySizeThresholdMB and mailDirectoryEventMaxFlushTimeSec to configure
|
||||
/// additional maildir format settings.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
@@ -157,6 +172,42 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectory will provide the base path used for
|
||||
/// saving logs using the maildir format.
|
||||
/// Defaults to /var/db/santa/mail.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *mailDirectory;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the mailDirectory.
|
||||
/// Defaults to 100.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectoryFileSizeThresholdKB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the mailDirectory.
|
||||
/// Defaults to 500.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger mailDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, mailDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// of time an event will be stored in memory before being written to disk.
|
||||
/// Defaults to 5.0.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) float mailDirectoryEventMaxFlushTimeSec;
|
||||
|
||||
///
|
||||
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
|
||||
/// has been overriden, this is the host's UUID.
|
||||
@@ -164,16 +215,6 @@
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
|
||||
///
|
||||
/// Use the bundled SystemExtension on macOS 10.15+, defaults to YES.
|
||||
/// Disable to continue using the bundled KEXT.
|
||||
/// This is a one way switch, if this is ever true on macOS 10.15+ the KEXT will be deleted.
|
||||
/// This gives admins control over the timing of switching to the SystemExtension. The intended use
|
||||
/// case is to have an MDM deliver the requisite SystemExtension and TCC profiles before attempting
|
||||
/// to load.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSystemExtension;
|
||||
|
||||
///
|
||||
/// Use an internal cache for decisions instead of relying on the caching
|
||||
/// mechanism built-in to the EndpointSecurity framework. This may increase
|
||||
@@ -235,6 +276,20 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is denied
|
||||
/// from the BlockUSB configuration setting. If not configured, a reasonable
|
||||
/// default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is forcibly
|
||||
/// remounted to a different set of permissions from the BlockUSB and RemountUSBMode
|
||||
/// configuration settings. If not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *remountUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// The notification text to display when the client goes into MONITOR mode.
|
||||
/// Defaults to "Switching into Monitor mode"
|
||||
@@ -427,6 +482,11 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportInterval;
|
||||
|
||||
///
|
||||
/// Duration in seconds for metrics export timeout. Defaults to 30;
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportTimeout;
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
///
|
||||
|
||||
@@ -66,6 +66,9 @@ static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
|
||||
static NSString *const kRemountUSBBlockMessage = @"RemountUSBBlockMessage";
|
||||
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
@@ -77,10 +80,13 @@ static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters
|
||||
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
static NSString *const kEventLogPath = @"EventLogPath";
|
||||
static NSString *const kMailDirectory = @"MailDirectory";
|
||||
static NSString *const kMailDirectoryFileSizeThresholdKB = @"MailDirectoryFileSizeThresholdKB";
|
||||
static NSString *const kMailDirectorySizeThresholdMB = @"MailDirectorySizeThresholdMB";
|
||||
static NSString *const kMailDirectoryEventMaxFlushTimeSec = @"MailDirectoryEventMaxFlushTimeSec";
|
||||
|
||||
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
|
||||
|
||||
static NSString *const kEnableSystemExtension = @"EnableSystemExtension";
|
||||
static NSString *const kEnableSysxCache = @"EnableSysxCache";
|
||||
|
||||
static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
|
||||
@@ -96,6 +102,7 @@ static NSString *const kFCMAPIKey = @"FCMAPIKey";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kFailClosedKey = @"FailClosed";
|
||||
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
|
||||
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
|
||||
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
|
||||
@@ -109,6 +116,7 @@ static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
|
||||
static NSString *const kMetricFormat = @"MetricFormat";
|
||||
static NSString *const kMetricURL = @"MetricURL";
|
||||
static NSString *const kMetricExportInterval = @"MetricExportInterval";
|
||||
static NSString *const kMetricExportTimeout = @"MetricExportTimeout";
|
||||
static NSString *const kMetricExtraLabels = @"MetricExtraLabels";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
@@ -142,6 +150,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kFailClosedKey : number,
|
||||
kEnableTransitiveRulesKey : number,
|
||||
kEnableTransitiveRulesKeyDeprecated : number,
|
||||
kFileChangesRegexKey : re,
|
||||
@@ -160,6 +169,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kEventDetailTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kBannedUSBBlockMessage : string,
|
||||
kRemountUSBBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kSyncBaseURLKey : string,
|
||||
@@ -178,8 +189,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
kMailDirectory : string,
|
||||
kMailDirectoryFileSizeThresholdKB : number,
|
||||
kMailDirectorySizeThresholdMB : number,
|
||||
kMailDirectoryEventMaxFlushTimeSec : number,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSystemExtension : number,
|
||||
kEnableSysxCache : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
@@ -191,6 +205,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kMetricFormat : string,
|
||||
kMetricURL : string,
|
||||
kMetricExportInterval : number,
|
||||
kMetricExportTimeout : number,
|
||||
kMetricExtraLabels : dictionary,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
@@ -355,6 +370,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectory {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryFileSizeThresholdKB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectorySizeThresholdMB {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMailDirectoryEventMaxFlushTimeSec {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -363,10 +394,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -429,6 +456,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)failClosed {
|
||||
NSNumber *n = self.configState[kFailClosedKey];
|
||||
if (n) return [n boolValue];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableTransitiveRules {
|
||||
NSNumber *n = self.syncState[kEnableTransitiveRulesKey];
|
||||
if (n) return [n boolValue];
|
||||
@@ -553,6 +586,21 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return self.configState[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)bannedUSBBlockMessage {
|
||||
if (!self.configState[kBannedUSBBlockMessage]) {
|
||||
return @"The following device has been blocked from mounting.";
|
||||
}
|
||||
|
||||
return self.configState[kBannedUSBBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)remountUSBBlockMessage {
|
||||
if (!self.configState[kRemountUSBBlockMessage]) {
|
||||
return @"The following device has been remounted with reduced permissions.";
|
||||
}
|
||||
return self.configState[kRemountUSBBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configState[kModeNotificationMonitor];
|
||||
}
|
||||
@@ -640,30 +688,47 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
|
||||
- (SNTEventLogType)eventLogType {
|
||||
NSString *s = [self.configState[kEventLogType] lowercaseString];
|
||||
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
|
||||
NSString *logType = [self.configState[kEventLogType] lowercaseString];
|
||||
if ([logType isEqualToString:@"protobuf"]) {
|
||||
return SNTEventLogTypeProtobuf;
|
||||
} else if ([logType isEqualToString:@"syslog"]) {
|
||||
return SNTEventLogTypeSyslog;
|
||||
} else {
|
||||
return SNTEventLogTypeFilelog;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
- (NSString *)mailDirectory {
|
||||
return self.configState[kMailDirectory] ?: @"/var/db/santa/mail";
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectoryFileSizeThresholdKB {
|
||||
return self.configState[kMailDirectoryFileSizeThresholdKB]
|
||||
? [self.configState[kMailDirectoryFileSizeThresholdKB] unsignedIntegerValue]
|
||||
: 100;
|
||||
}
|
||||
|
||||
- (NSUInteger)mailDirectorySizeThresholdMB {
|
||||
return self.configState[kMailDirectorySizeThresholdMB]
|
||||
? [self.configState[kMailDirectorySizeThresholdMB] unsignedIntegerValue]
|
||||
: 500;
|
||||
}
|
||||
|
||||
- (float)mailDirMaxFlushTime {
|
||||
return self.configState[kMailDirectoryEventMaxFlushTimeSec]
|
||||
? [self.configState[kMailDirectoryEventMaxFlushTimeSec] floatValue]
|
||||
: 5.0;
|
||||
}
|
||||
|
||||
- (BOOL)enableMachineIDDecoration {
|
||||
NSNumber *number = self.configState[kEnableMachineIDDecoration];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableSystemExtension {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:@"/Library/Extensions/santa-driver.kext"]) return YES;
|
||||
NSNumber *number = self.configState[kEnableSystemExtension];
|
||||
return number ? [number boolValue] : YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)enableSysxCache {
|
||||
NSNumber *number = self.configState[kEnableSysxCache];
|
||||
return number ? [number boolValue] : YES;
|
||||
@@ -751,6 +816,16 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [configuredInterval unsignedIntegerValue];
|
||||
}
|
||||
|
||||
// Returns a default value of 30 (for 30 seconds).
|
||||
- (NSUInteger)metricExportTimeout {
|
||||
NSNumber *configuredInterval = self.configState[kMetricExportTimeout];
|
||||
|
||||
if (configuredInterval == nil) {
|
||||
return 30;
|
||||
}
|
||||
return [configuredInterval unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (NSDictionary *)extraMetricLabels {
|
||||
return self.configState[kMetricExtraLabels];
|
||||
}
|
||||
|
||||
27
Source/common/SNTDeviceEvent.h
Normal file
27
Source/common/SNTDeviceEvent.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Copyright 2022 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SNTDeviceEvent : NSObject <NSSecureCoding>
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname;
|
||||
|
||||
@property NSString *mntonname;
|
||||
@property NSString *mntfromname;
|
||||
@property NSArray<NSString *> *remountArgs;
|
||||
|
||||
- (NSString *)readableRemountArgs;
|
||||
|
||||
@end
|
||||
63
Source/common/SNTDeviceEvent.m
Normal file
63
Source/common/SNTDeviceEvent.m
Normal file
@@ -0,0 +1,63 @@
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
|
||||
@implementation SNTDeviceEvent
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
|
||||
#define ENCODE(obj, key) \
|
||||
if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = mntonname;
|
||||
_mntfromname = mntfromname;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.mntonname, @"mntonname");
|
||||
ENCODE(self.mntfromname, @"mntfromname");
|
||||
ENCODE(self.remountArgs, @"remountArgs");
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = DECODE(NSString, @"mntonname");
|
||||
_mntfromname = DECODE(NSString, @"mntfromname");
|
||||
_remountArgs = DECODEARRAY(NSString, @"remountArgs");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTDeviceEvent '%@' -> '%@' (with permissions: [%@]",
|
||||
self.mntfromname, self.mntonname,
|
||||
[self.remountArgs componentsJoinedByString:@", "]];
|
||||
}
|
||||
|
||||
- (NSString *)readableRemountArgs {
|
||||
NSMutableArray<NSString *> *readable = [NSMutableArray array];
|
||||
for (NSString *arg in self.remountArgs) {
|
||||
if ([arg isEqualToString:@"rdonly"]) {
|
||||
[readable addObject:@"read-only"];
|
||||
} else if ([arg isEqualToString:@"noexec"]) {
|
||||
[readable addObject:@"block executables"];
|
||||
} else {
|
||||
[readable addObject:arg];
|
||||
}
|
||||
}
|
||||
return [readable componentsJoinedByString:@", "];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -13,27 +13,12 @@
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Logging definitions, for both kernel and user space.
|
||||
/// Logging definitions
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__LOGGING_H
|
||||
#define SANTA__COMMON__LOGGING_H
|
||||
|
||||
#ifdef KERNEL
|
||||
|
||||
#include <IOKit/IOLib.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#else // DEBUG
|
||||
#define LOGD(format, ...)
|
||||
#endif // DEBUG
|
||||
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
|
||||
#else // KERNEL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -68,6 +53,4 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
#endif // SANTA__COMMON__LOGGING_H
|
||||
|
||||
@@ -14,13 +14,6 @@
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
|
||||
#else
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <mutex>
|
||||
@@ -46,25 +39,14 @@
|
||||
|
||||
#define lck_mtx_lock(l) l->lock()
|
||||
#define lck_mtx_unlock(l) l->unlock()
|
||||
#endif // KERNEL
|
||||
|
||||
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
max_nodes_ = max_nodes;
|
||||
|
||||
#ifdef KERNEL
|
||||
spt_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
spt_lock_grp_ =
|
||||
lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
|
||||
spt_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
spt_add_lock_ = lck_mtx_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
#else
|
||||
pthread_rwlock_init(&spt_lock_, nullptr);
|
||||
spt_add_lock_ = new std::mutex;
|
||||
#endif
|
||||
}
|
||||
|
||||
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
@@ -241,32 +223,5 @@ SNTPrefixTree::~SNTPrefixTree() {
|
||||
root_ = nullptr;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
|
||||
#ifdef KERNEL
|
||||
if (spt_lock_) {
|
||||
lck_rw_free(spt_lock_, spt_lock_grp_);
|
||||
spt_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_add_lock_) {
|
||||
lck_mtx_free(spt_add_lock_, spt_lock_grp_);
|
||||
spt_add_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_attr_) {
|
||||
lck_attr_free(spt_lock_attr_);
|
||||
spt_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_) {
|
||||
lck_grp_free(spt_lock_grp_);
|
||||
spt_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_attr_) {
|
||||
lck_grp_attr_free(spt_lock_grp_attr_);
|
||||
spt_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
#else
|
||||
pthread_rwlock_destroy(&spt_lock_);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -18,15 +18,11 @@
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
#else
|
||||
// Support for unit testing.
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <mutex>
|
||||
#endif // KERNEL
|
||||
|
||||
///
|
||||
/// SantaPrefixTree is a simple prefix tree implementation.
|
||||
@@ -88,16 +84,8 @@ class SNTPrefixTree {
|
||||
uint32_t max_nodes_;
|
||||
uint32_t node_count_;
|
||||
|
||||
#ifdef KERNEL
|
||||
lck_grp_t *spt_lock_grp_;
|
||||
lck_grp_attr_t *spt_lock_grp_attr_;
|
||||
lck_attr_t *spt_lock_attr_;
|
||||
lck_rw_t *spt_lock_;
|
||||
lck_mtx_t *spt_add_lock_;
|
||||
#else // KERNEL
|
||||
pthread_rwlock_t spt_lock_;
|
||||
std::mutex *spt_add_lock_;
|
||||
#endif // KERNEL
|
||||
};
|
||||
|
||||
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */
|
||||
|
||||
@@ -49,4 +49,9 @@
|
||||
///
|
||||
+ (NSString *)longHostname;
|
||||
|
||||
///
|
||||
/// @return Model Identifier
|
||||
///
|
||||
+ (NSString *)modelIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
@implementation SNTSystemInfo
|
||||
|
||||
@@ -60,6 +61,13 @@
|
||||
return @(hostname);
|
||||
}
|
||||
|
||||
+ (NSString *)modelIdentifier {
|
||||
char model[32];
|
||||
size_t len = 32;
|
||||
sysctlbyname("hw.model", model, &len, NULL, 0);
|
||||
return @(model);
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
+ (NSDictionary *)_systemVersionDictionary {
|
||||
|
||||
@@ -27,13 +27,10 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
@implementation SNTXPCControlInterface
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
}
|
||||
return kBundleID;
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
}
|
||||
|
||||
+ (NSString *)systemExtensionID {
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
@class SNTDeviceEvent;
|
||||
|
||||
/// Protocol implemented by SantaGUI and utilized by santad
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@@ -20,28 +20,19 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <IOKit/IOLib.h>
|
||||
#else // KERNEL
|
||||
// Support for unit testing.
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
#define panic(args...) \
|
||||
printf(args); \
|
||||
printf("\n"); \
|
||||
abort()
|
||||
#define IOMallocAligned(sz, alignment) malloc(sz);
|
||||
#define IOFreeAligned(addr, sz) free(addr)
|
||||
#define OSTestAndSet OSAtomicTestAndSet
|
||||
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
|
||||
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
|
||||
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif // KERNEL
|
||||
|
||||
/**
|
||||
A type to specialize to help SantaCache with its hashing.
|
||||
@@ -88,8 +79,7 @@ class SantaCache {
|
||||
(1 << (32 -
|
||||
__builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
|
||||
if (unlikely(bucket_count_ > UINT32_MAX)) bucket_count_ = UINT32_MAX;
|
||||
buckets_ = (struct bucket *)IOMallocAligned(
|
||||
bucket_count_ * sizeof(struct bucket), 2);
|
||||
buckets_ = (struct bucket *)malloc(bucket_count_ * sizeof(struct bucket));
|
||||
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
@@ -98,7 +88,7 @@ class SantaCache {
|
||||
*/
|
||||
~SantaCache() {
|
||||
clear();
|
||||
IOFreeAligned(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
free(buckets_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +163,7 @@ class SantaCache {
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
struct entry *next_entry = entry->next;
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
free(entry);
|
||||
entry = next_entry;
|
||||
}
|
||||
}
|
||||
@@ -284,8 +274,8 @@ class SantaCache {
|
||||
} else {
|
||||
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
|
||||
}
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
OSDecrementAtomic(&count_);
|
||||
free(entry);
|
||||
OSAtomicDecrement64((volatile int64_t *)&count_);
|
||||
}
|
||||
|
||||
unlock(bucket);
|
||||
@@ -318,14 +308,13 @@ class SantaCache {
|
||||
|
||||
// Allocate a new entry, set the key and value, then put this new entry at
|
||||
// the head of this bucket's linked list.
|
||||
struct entry *new_entry =
|
||||
(struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
struct entry *new_entry = (struct entry *)malloc(sizeof(struct entry));
|
||||
bzero(new_entry, sizeof(struct entry));
|
||||
new_entry->key = key;
|
||||
new_entry->value = value;
|
||||
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
|
||||
OSIncrementAtomic(&count_);
|
||||
OSAtomicIncrement64((volatile int64_t *)&count_);
|
||||
|
||||
unlock(bucket);
|
||||
return true;
|
||||
@@ -335,7 +324,7 @@ class SantaCache {
|
||||
Lock a bucket. Spins until the lock is acquired.
|
||||
*/
|
||||
inline void lock(struct bucket *bucket) const {
|
||||
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
||||
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
||||
;
|
||||
}
|
||||
|
||||
@@ -343,7 +332,8 @@ class SantaCache {
|
||||
Unlock a bucket. Panics if the lock wasn't locked.
|
||||
*/
|
||||
inline void unlock(struct bucket *bucket) const {
|
||||
if (unlikely(OSTestAndClear(7, (volatile uint8_t *)&bucket->head))) {
|
||||
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
|
||||
0)) {
|
||||
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
|
||||
}
|
||||
}
|
||||
@@ -375,8 +365,6 @@ class SantaCache {
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef KERNEL
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
|
||||
145
Source/common/santa.proto
Normal file
145
Source/common/santa.proto
Normal file
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// !!! WARNING !!!
|
||||
// This proto is in beta format and subject to change.
|
||||
//
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option objc_class_prefix = "SNTPB";
|
||||
|
||||
package santa;
|
||||
|
||||
message ProcessInfo {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional int32 ppid = 3;
|
||||
optional int32 uid = 4;
|
||||
optional string user = 5;
|
||||
optional int32 gid = 6;
|
||||
optional string group = 7;
|
||||
}
|
||||
|
||||
message FileModification {
|
||||
enum Action {
|
||||
ACTION_UNKNOWN = 0;
|
||||
ACTION_DELETE = 1;
|
||||
ACTION_EXCHANGE = 2;
|
||||
ACTION_LINK = 3;
|
||||
ACTION_RENAME = 4;
|
||||
ACTION_WRITE = 5;
|
||||
}
|
||||
|
||||
optional Action action = 1;
|
||||
optional string path = 2;
|
||||
optional string newpath = 3;
|
||||
optional string process = 4;
|
||||
optional string process_path = 5;
|
||||
optional ProcessInfo process_info = 6;
|
||||
optional string machine_id = 7;
|
||||
}
|
||||
|
||||
message Execution {
|
||||
enum Decision {
|
||||
DECISION_UNKNOWN = 0;
|
||||
DECISION_ALLOW = 1;
|
||||
DECISION_DENY = 2;
|
||||
}
|
||||
|
||||
enum Reason {
|
||||
REASON_UNKNOWN = 0;
|
||||
REASON_BINARY = 1;
|
||||
REASON_CERT = 2;
|
||||
REASON_COMPILER = 3;
|
||||
REASON_NOT_RUNNING = 4;
|
||||
REASON_PENDING_TRANSITIVE = 5;
|
||||
REASON_SCOPE = 6;
|
||||
REASON_TEAM_ID = 7;
|
||||
REASON_TRANSITIVE = 8;
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
MODE_UNKNOWN = 0;
|
||||
MODE_LOCKDOWN = 1;
|
||||
MODE_MONITOR = 2;
|
||||
}
|
||||
|
||||
optional Decision decision = 1;
|
||||
optional Reason reason = 2;
|
||||
optional string explain = 3;
|
||||
optional string sha256 = 4;
|
||||
optional string cert_sha256 = 5;
|
||||
optional string cert_cn = 6;
|
||||
optional string quarantine_url = 7;
|
||||
optional ProcessInfo process_info = 8;
|
||||
optional Mode mode = 9;
|
||||
optional string path = 10;
|
||||
optional string original_path = 11;
|
||||
repeated string args = 12;
|
||||
optional string machine_id = 13;
|
||||
}
|
||||
|
||||
message DiskAppeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
optional string fs = 4;
|
||||
optional string model = 5;
|
||||
optional string serial = 6;
|
||||
optional string bus = 7;
|
||||
optional string dmg_path = 8;
|
||||
optional string appearance = 9;
|
||||
}
|
||||
|
||||
message DiskDisappeared {
|
||||
optional string mount = 1;
|
||||
optional string volume = 2;
|
||||
optional string bsd_name = 3;
|
||||
}
|
||||
|
||||
message Bundle {
|
||||
// This is the hash of the file within the bundle that triggered the event
|
||||
optional string sha256 = 1;
|
||||
// This is the hash of the hashes of all executables in the bundle
|
||||
optional string bundle_hash = 2;
|
||||
optional string bundle_name = 3;
|
||||
optional string bundle_id = 4;
|
||||
optional string bundle_path = 5;
|
||||
optional string path = 6;
|
||||
}
|
||||
|
||||
message Fork {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
message Exit {
|
||||
optional ProcessInfo process_info = 1;
|
||||
}
|
||||
|
||||
message Allowlist {
|
||||
optional int32 pid = 1;
|
||||
optional int32 pidversion = 2;
|
||||
optional string path = 3;
|
||||
optional string sha256 = 4;
|
||||
}
|
||||
|
||||
message SantaMessage {
|
||||
google.protobuf.Timestamp event_time = 1;
|
||||
|
||||
oneof message {
|
||||
FileModification file_modification = 2;
|
||||
Execution execution = 3;
|
||||
DiskAppeared disk_appeared = 4;
|
||||
DiskDisappeared disk_disappeared = 5;
|
||||
Bundle bundle = 6;
|
||||
Fork fork = 7;
|
||||
Exit exit = 8;
|
||||
Allowlist allowlist = 9;
|
||||
}
|
||||
}
|
||||
|
||||
message LogBatch {
|
||||
repeated google.protobuf.Any records = 1;
|
||||
}
|
||||
@@ -15,6 +15,10 @@ objc_library(
|
||||
"SNTAccessibleTextField.m",
|
||||
"SNTAppDelegate.h",
|
||||
"SNTAppDelegate.m",
|
||||
"SNTBinaryMessageWindowController.h",
|
||||
"SNTBinaryMessageWindowController.m",
|
||||
"SNTDeviceMessageWindowController.h",
|
||||
"SNTDeviceMessageWindowController.m",
|
||||
"SNTMessageWindow.h",
|
||||
"SNTMessageWindow.m",
|
||||
"SNTMessageWindowController.h",
|
||||
@@ -25,6 +29,7 @@ objc_library(
|
||||
],
|
||||
data = [
|
||||
"Resources/AboutWindow.xib",
|
||||
"Resources/DeviceMessageWindow.xib",
|
||||
"Resources/MessageWindow.xib",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,7 +17,7 @@
|
||||
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="1577"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -47,7 +47,7 @@ There are no user-configurable settings.</string>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SRu-Kf-vu5">
|
||||
<rect key="frame" x="130" y="21" width="111" height="32"/>
|
||||
<rect key="frame" x="129" y="21" width="113" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="99" id="JHv-2J-QSe"/>
|
||||
</constraints>
|
||||
@@ -60,7 +60,7 @@ There are no user-configurable settings.</string>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Udo-BY-n7e">
|
||||
<rect key="frame" x="240" y="21" width="111" height="32"/>
|
||||
<rect key="frame" x="239" y="21" width="113" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="99" id="2Xc-ax-2bV"/>
|
||||
</constraints>
|
||||
|
||||
193
Source/santa/Resources/DeviceMessageWindow.xib
Normal file
193
Source/santa/Resources/DeviceMessageWindow.xib
Normal file
@@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SNTDeviceMessageWindowController">
|
||||
<connections>
|
||||
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="515" height="318"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="515" height="318"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<userGuides>
|
||||
<userLayoutGuide location="344" affinity="minX"/>
|
||||
</userGuides>
|
||||
<subviews>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
|
||||
<rect key="frame" x="16" y="290" width="37" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="31" y="210" width="454" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="139" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device Name" id="KgY-X1-ESG">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="115" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device BSD Path" id="adC-be-Beh">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Device Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="139" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device Name" id="E7T-9h-ofr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.mntonname" id="bOu-gv-1Vh"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField toolTip="Device BSD Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="115" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device BSD Path" id="H1b-Ui-CYo">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.mntfromname" id="Sry-KY-HDb"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
|
||||
<rect key="frame" x="217" y="248" width="82" height="40"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
|
||||
<font key="font" metaFont="systemUltraLight" size="34"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="i8e-8n-tZS" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="91" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="a3r-ng-zxJ"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Remount Mode" id="4D0-8L-ihK">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Device BSD Path" verticalHuggingPriority="750" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kmz-be-oWf" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="91" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="cbM-i5-bJ9"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Remount Mode" id="qI0-Na-kxN">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.readableRemountArgs" id="bOu-gv-1Vl"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="202" y="18" width="112" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Ok" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
|
||||
</buttonCell>
|
||||
<accessibility description="Dismiss Dialog"/>
|
||||
<connections>
|
||||
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
|
||||
<constraint firstItem="kmz-be-oWf" firstAttribute="height" secondItem="i8e-8n-tZS" secondAttribute="height" id="6Ow-Sl-6Wc"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
|
||||
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="55" id="Nsl-zf-poH"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
|
||||
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
|
||||
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="22" id="dYg-zP-wh2"/>
|
||||
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
|
||||
<constraint firstItem="kmz-be-oWf" firstAttribute="centerY" secondItem="i8e-8n-tZS" secondAttribute="centerY" id="ilC-5x-LNe"/>
|
||||
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
|
||||
<constraint firstItem="i8e-8n-tZS" firstAttribute="centerY" secondItem="YNz-ka-cBi" secondAttribute="centerY" constant="24" id="uZs-XT-PuZ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="firstBaseline" secondItem="d9e-Wv-Y5H" secondAttribute="baseline" constant="24" id="xZu-AY-wX5"/>
|
||||
<constraint firstItem="i8e-8n-tZS" firstAttribute="top" secondItem="YNz-ka-cBi" secondAttribute="bottom" constant="8" symbolic="YES" id="ylw-3s-9JW"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="261.5" y="246"/>
|
||||
</window>
|
||||
<userDefaultsController representsSharedInstance="YES" id="iXx-cu-WYe"/>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -21,8 +21,8 @@
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="1577"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
@@ -41,7 +41,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="43" y="369" width="454" height="17"/>
|
||||
<rect key="frame" x="43" y="354" width="454" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
@@ -56,7 +56,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="297" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="283" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
@@ -70,7 +70,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="272" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="259" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
|
||||
</constraints>
|
||||
@@ -84,7 +84,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Binary Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="297" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="283" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
|
||||
</constraints>
|
||||
@@ -98,7 +98,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
|
||||
<rect key="frame" x="8" y="247" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="235" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -109,7 +109,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="247" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="235" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="Dem-wH-KHm"/>
|
||||
</constraints>
|
||||
@@ -127,7 +127,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
|
||||
<rect key="frame" x="62" y="248" width="15" height="15"/>
|
||||
<rect key="frame" x="62" y="235" width="15" height="15"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
|
||||
@@ -149,7 +149,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
|
||||
<rect key="frame" x="8" y="222" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="212" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -160,7 +160,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
|
||||
<rect key="frame" x="187" y="222" width="219" height="17"/>
|
||||
<rect key="frame" x="187" y="214" width="219" height="13"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
|
||||
</constraints>
|
||||
@@ -177,7 +177,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MhO-U0-MLR" userLabel="Label: Bundle Identifier">
|
||||
<rect key="frame" x="8" y="197" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="191" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bundle Identifier" id="LEe-u0-52o">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -195,7 +195,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
|
||||
<rect key="frame" x="8" y="157" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="156" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -206,7 +206,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="157" width="294" height="17"/>
|
||||
<rect key="frame" x="187" y="156" width="294" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
|
||||
</constraints>
|
||||
@@ -229,7 +229,7 @@
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
|
||||
<rect key="frame" x="8" y="132" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="132" width="142" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -240,7 +240,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="154" y="34" width="112" height="23"/>
|
||||
<rect key="frame" x="153" y="35" width="114" height="23"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
@@ -258,7 +258,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="272" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="259" width="315" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
|
||||
</constraints>
|
||||
@@ -272,7 +272,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
|
||||
<rect key="frame" x="113" y="80" width="315" height="29"/>
|
||||
<rect key="frame" x="110" y="80" width="319" height="29"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
|
||||
</constraints>
|
||||
@@ -285,7 +285,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="278" y="34" width="110" height="23"/>
|
||||
<rect key="frame" x="277" y="33" width="112" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
@@ -305,7 +305,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="132" width="294" height="17"/>
|
||||
<rect key="frame" x="187" y="132" width="294" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
@@ -322,14 +322,14 @@ DQ
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<progressIndicator wantsLayer="YES" canDrawConcurrently="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
|
||||
<rect key="frame" x="187" y="199" width="217" height="12"/>
|
||||
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
|
||||
<rect key="frame" x="187" y="193" width="217" height="12"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="217" id="M22-Dv-KIP"/>
|
||||
</constraints>
|
||||
</progressIndicator>
|
||||
<textField toolTip="Bundle SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xP7-jE-NF8">
|
||||
<rect key="frame" x="187" y="197" width="219" height="17"/>
|
||||
<rect key="frame" x="187" y="193" width="219" height="13"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="215" id="s7W-o9-2nN"/>
|
||||
</constraints>
|
||||
@@ -346,7 +346,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LHV-gV-vyf">
|
||||
<rect key="frame" x="187" y="182" width="219" height="17"/>
|
||||
<rect key="frame" x="187" y="180" width="219" height="13"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="215" id="LUu-Vd-peN"/>
|
||||
</constraints>
|
||||
@@ -357,7 +357,7 @@ DQ
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<box horizontalHuggingPriority="750" boxType="custom" borderType="line" title="Line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
|
||||
<rect key="frame" x="168" y="132" width="1" height="207"/>
|
||||
<rect key="frame" x="168" y="132" width="1" height="191"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
@@ -366,7 +366,7 @@ DQ
|
||||
<font key="titleFont" metaFont="system"/>
|
||||
</box>
|
||||
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="187" y="322" width="315" height="17"/>
|
||||
<rect key="frame" x="187" y="307" width="315" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="311" id="Pav-ZA-iAu"/>
|
||||
</constraints>
|
||||
@@ -384,7 +384,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
|
||||
<rect key="frame" x="8" y="322" width="142" height="17"/>
|
||||
<rect key="frame" x="8" y="307" width="142" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="8mA-zi-Ev7"/>
|
||||
</constraints>
|
||||
@@ -405,7 +405,7 @@ DQ
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
|
||||
<rect key="frame" x="229" y="408" width="82" height="41"/>
|
||||
<rect key="frame" x="229" y="392" width="82" height="40"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
|
||||
<font key="font" metaFont="systemUltraLight" size="34"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
||||
65
Source/santa/SNTBinaryMessageWindowController.h
Normal file
65
Source/santa/SNTBinaryMessageWindowController.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
@interface SNTBinaryMessageWindowController : SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender;
|
||||
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash;
|
||||
|
||||
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashLabel;
|
||||
|
||||
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashTitle;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
|
||||
|
||||
///
|
||||
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
|
||||
/// if it isn't needed or set its title if it is.
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *openEventButton;
|
||||
|
||||
///
|
||||
/// The execution event that this window is for
|
||||
///
|
||||
@property(readonly) SNTStoredEvent *event;
|
||||
|
||||
///
|
||||
/// The root progress object. Child nodes are vended to santad to report on work being done.
|
||||
///
|
||||
@property NSProgress *progress;
|
||||
|
||||
@end
|
||||
176
Source/santa/SNTBinaryMessageWindowController.m
Normal file
176
Source/santa/SNTBinaryMessageWindowController.m
Normal file
@@ -0,0 +1,176 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTBinaryMessageWindowController.h"
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
|
||||
@interface SNTBinaryMessageWindowController ()
|
||||
/// The custom message to display for this event
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
/// A 'friendly' string representing the certificate information
|
||||
@property(readonly, nonatomic) NSString *publisherInfo;
|
||||
|
||||
/// An optional message to display with this block.
|
||||
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
|
||||
|
||||
/// Reference to the "Application Name" label in the XIB. Used to remove if application
|
||||
/// doesn't have a CFBundleName.
|
||||
@property(weak) IBOutlet NSTextField *applicationNameLabel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTBinaryMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = message;
|
||||
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
|
||||
[_progress addObserver:self
|
||||
forKeyPath:@"fractionCompleted"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context {
|
||||
if ([keyPath isEqualToString:@"fractionCompleted"]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSProgress *progress = object;
|
||||
if (progress.fractionCompleted != 0.0) {
|
||||
self.hashingIndicator.indeterminate = NO;
|
||||
}
|
||||
self.hashingIndicator.doubleValue = progress.fractionCompleted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.event.needsBundleHash) {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.hashingIndicator removeFromSuperview];
|
||||
[self.foundFileCountLabel removeFromSuperview];
|
||||
} else {
|
||||
self.openEventButton.enabled = NO;
|
||||
self.hashingIndicator.indeterminate = YES;
|
||||
[self.hashingIndicator startAnimation:self];
|
||||
self.bundleHashLabel.hidden = YES;
|
||||
self.foundFileCountLabel.stringValue = @"";
|
||||
}
|
||||
|
||||
if (!self.event.fileBundleName) {
|
||||
[self.applicationNameLabel removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
return self.event.fileSHA256;
|
||||
}
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender {
|
||||
// SFCertificatePanel expects an NSArray of SecCertificateRef's
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
|
||||
for (MOLCertificate *cert in self.event.signingChain) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
|
||||
modalDelegate:nil
|
||||
didEndSelector:nil
|
||||
contextInfo:nil
|
||||
certificates:certArray
|
||||
showGroup:YES];
|
||||
}
|
||||
|
||||
- (IBAction)openEventDetails:(id)sender {
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
#pragma mark Generated properties
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
||||
if (![key isEqualToString:@"event"]) {
|
||||
return [NSSet setWithObject:@"event"];
|
||||
} else {
|
||||
return [NSSet set];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
MOLCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
|
||||
customMessage:self.customMessage];
|
||||
}
|
||||
|
||||
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
|
||||
// UI updates must happen on the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.event.idx isEqual:event.idx]) {
|
||||
if (bundleHash) {
|
||||
[self.bundleHashLabel setHidden:NO];
|
||||
} else {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.bundleHashTitle removeFromSuperview];
|
||||
}
|
||||
self.event.fileBundleHash = bundleHash;
|
||||
[self.foundFileCountLabel removeFromSuperview];
|
||||
[self.hashingIndicator setHidden:YES];
|
||||
[self.openEventButton setEnabled:YES];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
38
Source/santa/SNTDeviceMessageWindowController.h
Normal file
38
Source/santa/SNTDeviceMessageWindowController.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
@interface SNTDeviceMessageWindowController : SNTMessageWindowController
|
||||
|
||||
@property(weak) IBOutlet NSTextField *remountArgsLabel;
|
||||
@property(weak) IBOutlet NSTextField *remountArgsTitle;
|
||||
|
||||
// The device event this window is for.
|
||||
@property(readonly) SNTDeviceEvent *event;
|
||||
|
||||
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
57
Source/santa/SNTDeviceMessageWindowController.m
Normal file
57
Source/santa/SNTDeviceMessageWindowController.m
Normal file
@@ -0,0 +1,57 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTDeviceMessageWindowController.h"
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SNTDeviceMessageWindowController ()
|
||||
@property(copy, nullable) NSString *customMessage;
|
||||
@end
|
||||
|
||||
@implementation SNTDeviceMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message {
|
||||
self = [super initWithWindowNibName:@"DeviceMessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = message;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
if (!self.event.remountArgs || [self.event.remountArgs count] <= 0) {
|
||||
[self.remountArgsLabel removeFromSuperview];
|
||||
[self.remountArgsTitle removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage formatMessage:self.customMessage];
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
return self.event.mntonname;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,73 +1,21 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@protocol SNTMessageWindowControllerDelegate
|
||||
- (void)windowDidCloseSilenceHash:(NSString *)hash;
|
||||
@end
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
@interface SNTMessageWindowController : NSWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
|
||||
|
||||
- (IBAction)showWindow:(id)sender;
|
||||
- (IBAction)closeWindow:(id)sender;
|
||||
- (IBAction)showCertInfo:(id)sender;
|
||||
|
||||
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashLabel;
|
||||
/// Generate a distinct key for a given displayed event. This key is used for silencing future
|
||||
/// notifications.
|
||||
- (NSString *)messageHash;
|
||||
|
||||
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
|
||||
/// doesn't have a bundle hash.
|
||||
@property(weak) IBOutlet NSTextField *bundleHashTitle;
|
||||
/// Linked to checkbox in UI to prevent future notifications for the given event.
|
||||
@property BOOL silenceFutureNotifications;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
|
||||
|
||||
///
|
||||
/// Is displayed if calculating the bundle hash is taking a bit.
|
||||
///
|
||||
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
|
||||
|
||||
///
|
||||
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
|
||||
/// if it isn't needed or set its title if it is.
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *openEventButton;
|
||||
|
||||
///
|
||||
/// The execution event that this window is for
|
||||
///
|
||||
@property(readonly) SNTStoredEvent *event;
|
||||
|
||||
///
|
||||
/// The root progress object. Child nodes are vended to santad to report on work being done.
|
||||
///
|
||||
@property NSProgress *progress;
|
||||
|
||||
///
|
||||
/// The delegate to inform when the notification is dismissed
|
||||
///
|
||||
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,117 +1,13 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/santa/SNTMessageWindow.h"
|
||||
|
||||
@interface SNTMessageWindowController ()
|
||||
/// The custom message to display for this event
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
/// A 'friendly' string representing the certificate information
|
||||
@property(readonly, nonatomic) NSString *publisherInfo;
|
||||
|
||||
/// An optional message to display with this block.
|
||||
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
|
||||
|
||||
/// Reference to the "Application Name" label in the XIB. Used to remove if application
|
||||
/// doesn't have a CFBundleName.
|
||||
@property(weak) IBOutlet NSTextField *applicationNameLabel;
|
||||
|
||||
/// Linked to checkbox in UI to prevent future notifications for this binary.
|
||||
@property BOOL silenceFutureNotifications;
|
||||
@end
|
||||
|
||||
@implementation SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = message;
|
||||
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
|
||||
[_progress addObserver:self
|
||||
forKeyPath:@"fractionCompleted"
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:NULL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context {
|
||||
if ([keyPath isEqualToString:@"fractionCompleted"]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSProgress *progress = object;
|
||||
if (progress.fractionCompleted != 0.0) {
|
||||
self.hashingIndicator.indeterminate = NO;
|
||||
}
|
||||
self.hashingIndicator.doubleValue = progress.fractionCompleted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
[self.window setLevel:NSPopUpMenuWindowLevel];
|
||||
[self.window setMovableByWindowBackground:YES];
|
||||
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.event.needsBundleHash) {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.hashingIndicator removeFromSuperview];
|
||||
[self.foundFileCountLabel removeFromSuperview];
|
||||
} else {
|
||||
self.openEventButton.enabled = NO;
|
||||
self.hashingIndicator.indeterminate = YES;
|
||||
[self.hashingIndicator startAnimation:self];
|
||||
self.bundleHashLabel.hidden = YES;
|
||||
self.foundFileCountLabel.stringValue = @"";
|
||||
}
|
||||
|
||||
if (!self.event.fileBundleName) {
|
||||
[self.applicationNameLabel removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showWindow:(id)sender {
|
||||
[(SNTMessageWindow *)self.window fadeIn:sender];
|
||||
}
|
||||
|
||||
- (IBAction)closeWindow:(id)sender {
|
||||
[self.progress cancel];
|
||||
[(SNTMessageWindow *)self.window fadeOut:sender];
|
||||
}
|
||||
|
||||
@@ -119,60 +15,20 @@
|
||||
if (!self.delegate) return;
|
||||
|
||||
if (self.silenceFutureNotifications) {
|
||||
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
|
||||
[self.delegate windowDidCloseSilenceHash:[self messageHash]];
|
||||
} else {
|
||||
[self.delegate windowDidCloseSilenceHash:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender {
|
||||
// SFCertificatePanel expects an NSArray of SecCertificateRef's
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
|
||||
for (MOLCertificate *cert in self.event.signingChain) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
|
||||
modalDelegate:nil
|
||||
didEndSelector:nil
|
||||
contextInfo:nil
|
||||
certificates:certArray
|
||||
showGroup:YES];
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
[self.window setLevel:NSPopUpMenuWindowLevel];
|
||||
[self.window setMovableByWindowBackground:YES];
|
||||
}
|
||||
|
||||
- (IBAction)openEventDetails:(id)sender {
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
#pragma mark Generated properties
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
||||
if (![key isEqualToString:@"event"]) {
|
||||
return [NSSet setWithObject:@"event"];
|
||||
} else {
|
||||
return [NSSet set];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
MOLCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
|
||||
customMessage:self.customMessage];
|
||||
- (NSString *)messageHash {
|
||||
return @"";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/santa/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/santa/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
///
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santa/SNTMessageWindowController.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
|
||||
@@ -57,13 +59,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
self.currentWindowController = nil;
|
||||
|
||||
if ([self.pendingNotifications count]) {
|
||||
self.currentWindowController = [self.pendingNotifications firstObject];
|
||||
[self.currentWindowController showWindow:self];
|
||||
if (self.currentWindowController.event.needsBundleHash) {
|
||||
dispatch_async(self.hashBundleBinariesQueue, ^{
|
||||
[self hashBundleBinariesForEvent:self.currentWindowController.event];
|
||||
});
|
||||
}
|
||||
[self showQueuedWindow];
|
||||
} else {
|
||||
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
|
||||
[bc resume];
|
||||
@@ -85,6 +81,130 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[ud setObject:d forKey:silencedNotificationsKey];
|
||||
}
|
||||
|
||||
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
|
||||
for (SNTMessageWindowController *msg in self.pendingNotifications) {
|
||||
if ([msg messageHash] == [pendingMsg messageHash]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
|
||||
NSString *messageHash = [pendingMsg messageHash];
|
||||
if ([self notificationAlreadyQueued:pendingMsg]) return;
|
||||
|
||||
// See if this message is silenced.
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
|
||||
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
|
||||
LOGI(@"Notification silence: date is in the future, ignoring");
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
|
||||
LOGI(@"Notification silence: date is more than one day ago, ignoring");
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else {
|
||||
LOGI(@"Notification silence: dropping notification for %@", messageHash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
if (!self.currentWindowController) {
|
||||
[self showQueuedWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showQueuedWindow {
|
||||
// Notifications arrive on a background thread but UI updates must happen on the main thread.
|
||||
// This includes making windows.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// If a notification isn't currently being displayed, display the incoming one.
|
||||
// This check will generally be redundant, as we'd generally want to check this prior to
|
||||
// starting work on the main thread.
|
||||
if (!self.currentWindowController) {
|
||||
self.currentWindowController = [self.pendingNotifications firstObject];
|
||||
[self.currentWindowController showWindow:self];
|
||||
|
||||
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
|
||||
SNTBinaryMessageWindowController *controller =
|
||||
(SNTBinaryMessageWindowController *)self.currentWindowController;
|
||||
dispatch_async(self.hashBundleBinariesQueue, ^{
|
||||
[self hashBundleBinariesForEvent:controller.event withController:controller];
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
|
||||
withController:(SNTBinaryMessageWindowController *)withController {
|
||||
withController.foundFileCountLabel.stringValue = @"Searching for files...";
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
|
||||
bc.acceptedHandler = ^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
};
|
||||
[bc resume];
|
||||
|
||||
// Wait a max of 5 secs for the bundle service
|
||||
// Otherwise abandon bundle hashing and display the blockable event.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
[withController updateBlockNotification:event withBundleHash:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[[bc remoteObjectProxy] setNotificationListener:self.notificationListener];
|
||||
|
||||
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
|
||||
[withController.progress becomeCurrentWithPendingUnitCount:100];
|
||||
|
||||
// Start hashing. Progress is reported to the root NSProgress
|
||||
// (currentWindowController.progress).
|
||||
[[bc remoteObjectProxy]
|
||||
hashBundleBinariesForEvent:event
|
||||
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
|
||||
// Revert to displaying the blockable event if we fail to calculate the
|
||||
// bundle hash
|
||||
if (!bh)
|
||||
return [withController updateBlockNotification:event
|
||||
withBundleHash:nil];
|
||||
|
||||
event.fileBundleHash = bh;
|
||||
event.fileBundleBinaryCount = @(events.count);
|
||||
event.fileBundleHashMilliseconds = ms;
|
||||
event.fileBundleExecutableRelPath =
|
||||
[events.firstObject fileBundleExecutableRelPath];
|
||||
for (SNTStoredEvent *se in events) {
|
||||
se.fileBundleHash = bh;
|
||||
se.fileBundleBinaryCount = @(events.count);
|
||||
se.fileBundleHashMilliseconds = ms;
|
||||
}
|
||||
|
||||
// Send the results to santad. It will decide if they need to be
|
||||
// synced.
|
||||
MOLXPCConnection *daemonConn =
|
||||
[SNTXPCControlInterface configuredConnection];
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] syncBundleEvent:event
|
||||
relatedEvents:events];
|
||||
[daemonConn invalidate];
|
||||
|
||||
// Update the UI with the bundle hash. Also make the openEventButton
|
||||
// available.
|
||||
[withController updateBlockNotification:event withBundleHash:bh];
|
||||
|
||||
[bc invalidate];
|
||||
}];
|
||||
|
||||
[withController.progress resignCurrent];
|
||||
}
|
||||
|
||||
#pragma mark SNTNotifierXPC protocol methods
|
||||
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode {
|
||||
@@ -113,52 +233,15 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
}
|
||||
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
// See if this binary is already in the list of pending notifications.
|
||||
NSPredicate *predicate =
|
||||
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
|
||||
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
|
||||
|
||||
// See if this binary is silenced.
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
|
||||
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
|
||||
LOGI(@"Notification silence: date is in the future, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
|
||||
LOGI(@"Notification silence: date is more than one day ago, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
} else {
|
||||
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Notifications arrive on a background thread but UI updates must happen on the main thread.
|
||||
// This includes making windows.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SNTMessageWindowController *pendingMsg =
|
||||
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
SNTBinaryMessageWindowController *pendingMsg =
|
||||
[[SNTBinaryMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
|
||||
// If a notification isn't currently being displayed, display the incoming one.
|
||||
if (!self.currentWindowController) {
|
||||
self.currentWindowController = pendingMsg;
|
||||
[pendingMsg showWindow:nil];
|
||||
if (self.currentWindowController.event.needsBundleHash) {
|
||||
dispatch_async(self.hashBundleBinariesQueue, ^{
|
||||
[self hashBundleBinariesForEvent:self.currentWindowController.event];
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
|
||||
@@ -169,98 +252,35 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
}
|
||||
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
SNTDeviceMessageWindowController *pendingMsg =
|
||||
[[SNTDeviceMessageWindowController alloc] initWithEvent:event message:message];
|
||||
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
#pragma mark SNTBundleNotifierXPC protocol methods
|
||||
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
binaryCount:(uint64_t)binaryCount
|
||||
fileCount:(uint64_t)fileCount
|
||||
hashedCount:(uint64_t)hashedCount {
|
||||
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.currentWindowController.foundFileCountLabel.stringValue =
|
||||
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
|
||||
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
|
||||
});
|
||||
}
|
||||
}
|
||||
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
|
||||
SNTBinaryMessageWindowController *controller =
|
||||
(SNTBinaryMessageWindowController *)self.currentWindowController;
|
||||
|
||||
#pragma mark SNTBundleNotifierXPC helper methods
|
||||
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event {
|
||||
self.currentWindowController.foundFileCountLabel.stringValue = @"Searching for files...";
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
|
||||
bc.acceptedHandler = ^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
};
|
||||
[bc resume];
|
||||
|
||||
// Wait a max of 5 secs for the bundle service
|
||||
// Otherwise abandon bundle hashing and display the blockable event.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
[self updateBlockNotification:event withBundleHash:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[[bc remoteObjectProxy] setNotificationListener:self.notificationListener];
|
||||
|
||||
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
|
||||
[self.currentWindowController.progress becomeCurrentWithPendingUnitCount:100];
|
||||
|
||||
// Start hashing. Progress is reported to the root NSProgress (currentWindowController.progress).
|
||||
[[bc remoteObjectProxy]
|
||||
hashBundleBinariesForEvent:event
|
||||
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
|
||||
// Revert to displaying the blockable event if we fail to calculate the
|
||||
// bundle hash
|
||||
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
|
||||
|
||||
event.fileBundleHash = bh;
|
||||
event.fileBundleBinaryCount = @(events.count);
|
||||
event.fileBundleHashMilliseconds = ms;
|
||||
event.fileBundleExecutableRelPath =
|
||||
[events.firstObject fileBundleExecutableRelPath];
|
||||
for (SNTStoredEvent *se in events) {
|
||||
se.fileBundleHash = bh;
|
||||
se.fileBundleBinaryCount = @(events.count);
|
||||
se.fileBundleHashMilliseconds = ms;
|
||||
}
|
||||
|
||||
// Send the results to santad. It will decide if they need to be
|
||||
// synced.
|
||||
MOLXPCConnection *daemonConn =
|
||||
[SNTXPCControlInterface configuredConnection];
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] syncBundleEvent:event
|
||||
relatedEvents:events];
|
||||
[daemonConn invalidate];
|
||||
|
||||
// Update the UI with the bundle hash. Also make the openEventButton
|
||||
// available.
|
||||
[self updateBlockNotification:event withBundleHash:bh];
|
||||
|
||||
[bc invalidate];
|
||||
}];
|
||||
|
||||
[self.currentWindowController.progress resignCurrent];
|
||||
}
|
||||
|
||||
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
|
||||
if (bundleHash) {
|
||||
[self.currentWindowController.bundleHashLabel setHidden:NO];
|
||||
} else {
|
||||
[self.currentWindowController.bundleHashLabel removeFromSuperview];
|
||||
[self.currentWindowController.bundleHashTitle removeFromSuperview];
|
||||
}
|
||||
self.currentWindowController.event.fileBundleHash = bundleHash;
|
||||
[self.currentWindowController.foundFileCountLabel removeFromSuperview];
|
||||
[self.currentWindowController.hashingIndicator setHidden:YES];
|
||||
[self.currentWindowController.openEventButton setEnabled:YES];
|
||||
if ([controller.event.idx isEqual:event.idx]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
controller.foundFileCountLabel.stringValue =
|
||||
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
|
||||
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -67,10 +67,6 @@ int main(int argc, const char *argv[]) {
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
|
||||
OSSystemExtensionRequest *req;
|
||||
if (sysxOperation.intValue == 1) {
|
||||
if (![[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
NSLog(@"EnableSystemExtension is disabled");
|
||||
exit(1);
|
||||
}
|
||||
NSLog(@"Requesting SystemExtension activation");
|
||||
req = [OSSystemExtensionRequest activationRequestForExtension:e queue:q];
|
||||
} else if (sysxOperation.intValue == 2) {
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
load(
|
||||
"@build_bazel_rules_apple//apple:macos.bzl",
|
||||
"macos_command_line_application",
|
||||
"macos_kernel_extension",
|
||||
)
|
||||
load("//:helper.bzl", "run_command")
|
||||
load("//:version.bzl", "SANTA_VERSION")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "santa_driver_lib",
|
||||
srcs = [
|
||||
"SantaDecisionManager.cc",
|
||||
"SantaDecisionManager.h",
|
||||
"SantaDriver.cc",
|
||||
"SantaDriver.h",
|
||||
"SantaDriverClient.cc",
|
||||
"SantaDriverClient.h",
|
||||
"main.cc",
|
||||
],
|
||||
copts = [
|
||||
"-mkernel",
|
||||
"-fapple-kext",
|
||||
"-Wno-ossharedptr-misuse",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = [
|
||||
"KERNEL",
|
||||
"APPLE",
|
||||
"NeXT",
|
||||
"SANTA_VERSION=" + SANTA_VERSION,
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLoggingKernel",
|
||||
"//Source/common:SNTPrefixTreeKernel",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
macos_kernel_extension(
|
||||
name = "santa_driver",
|
||||
bundle_id = "com.google.santa-driver",
|
||||
bundle_name = "santa-driver",
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.9",
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":santa_driver_lib"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "kernel_tests_lib",
|
||||
srcs = ["kernel_tests.mm"],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
"IOKit",
|
||||
],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
)
|
||||
|
||||
macos_command_line_application(
|
||||
name = "kernel_tests_bin",
|
||||
bundle_id = "com.google.santa.KernelTests",
|
||||
minimum_os_version = "10.9",
|
||||
deps = [":kernel_tests_lib"],
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "kernel_tests",
|
||||
srcs = [
|
||||
":kernel_tests_bin",
|
||||
":santa_driver",
|
||||
],
|
||||
cmd = """
|
||||
env
|
||||
function sigint() {
|
||||
echo "\nInterrupted, unloading driver."
|
||||
sudo kextunload -b com.google.santa-driver >/dev/null
|
||||
exit 1
|
||||
}
|
||||
unzip -o $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
echo "Launching Kernel Tests as root. You may be prompted for your sudo password."
|
||||
trap sigint INT
|
||||
sudo $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/kernel_tests_bin
|
||||
echo "Tests complete."
|
||||
if kextstat | grep com.google.santa-driver; then
|
||||
sudo kextunload -b com.google.santa-driver >/dev/null
|
||||
fi
|
||||
""",
|
||||
)
|
||||
@@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>santa-driver</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa-driver</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>santa-driver</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>KEXT</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>IOKitPersonalities</key>
|
||||
<dict>
|
||||
<key>SantaDriver</key>
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa-driver</string>
|
||||
<key>IOClass</key>
|
||||
<string>com_google_SantaDriver</string>
|
||||
<key>IOMatchCategory</key>
|
||||
<string>com_google_SantaDriver</string>
|
||||
<key>IOProviderClass</key>
|
||||
<string>IOResources</string>
|
||||
<key>IOResourceMatch</key>
|
||||
<string>IOKit</string>
|
||||
<key>IOUserClientClass</key>
|
||||
<string>com_google_SantaDriverClient</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Google LLC.</string>
|
||||
<key>OSBundleLibraries</key>
|
||||
<dict>
|
||||
<key>com.apple.kpi.bsd</key>
|
||||
<string>9.0.0</string>
|
||||
<key>com.apple.kpi.iokit</key>
|
||||
<string>9.0.0</string>
|
||||
<key>com.apple.kpi.libkern</key>
|
||||
<string>9.0.0</string>
|
||||
<key>com.apple.kpi.mach</key>
|
||||
<string>9.0.0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,843 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/santa_driver/SantaDecisionManager.h"
|
||||
|
||||
// This is a made-up KAUTH_FILEOP constant which represents a
|
||||
// KAUTH_VNODE_WRITE_DATA event that gets passed to SantaDecisionManager's
|
||||
// FileOpCallback method. The KAUTH_FILEOP_* constants are defined in
|
||||
// sys/kauth.h and run from 1--7. KAUTH_VNODE_WRITE_DATA is already defined as
|
||||
// 4 so it overlaps with the other KAUTH_FILEOP_* constants and can't be used.
|
||||
// We define KAUTH_FILEOP_WRITE as something much greater than 7.
|
||||
#define KAUTH_FILEOP_WRITE 100
|
||||
|
||||
#define super OSObject
|
||||
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
|
||||
|
||||
#pragma mark Object Lifecycle
|
||||
|
||||
template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t) {
|
||||
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::init() {
|
||||
if (!super::init()) return false;
|
||||
|
||||
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
|
||||
sdm_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
|
||||
root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(5000, 2);
|
||||
non_root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(500, 2);
|
||||
vnode_pid_map_ = new SantaCache<santa_vnode_id_t, uint64_t>(2000, 5);
|
||||
compiler_pid_set_ = new SantaCache<pid_t, pid_t>(500, 5);
|
||||
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
if (!decision_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
if (!log_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
client_pid_ = 0;
|
||||
root_fsid_ = 0;
|
||||
|
||||
// Setup file modification prefix filter.
|
||||
filemod_prefix_filter_ = new SNTPrefixTree();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::free() {
|
||||
delete root_decision_cache_;
|
||||
delete non_root_decision_cache_;
|
||||
delete vnode_pid_map_;
|
||||
|
||||
StopPidMonitorThreads();
|
||||
|
||||
if (decision_dataqueue_lock_) {
|
||||
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
|
||||
decision_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (log_dataqueue_lock_) {
|
||||
lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
|
||||
log_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_attr_) {
|
||||
lck_attr_free(sdm_lock_attr_);
|
||||
sdm_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_) {
|
||||
lck_grp_free(sdm_lock_grp_);
|
||||
sdm_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_attr_) {
|
||||
lck_grp_attr_free(sdm_lock_grp_attr_);
|
||||
sdm_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
|
||||
OSSafeReleaseNULL(decision_dataqueue_);
|
||||
OSSafeReleaseNULL(log_dataqueue_);
|
||||
|
||||
delete filemod_prefix_filter_;
|
||||
|
||||
super::free();
|
||||
}
|
||||
|
||||
#pragma mark Client Management
|
||||
|
||||
void SantaDecisionManager::ConnectClient(pid_t pid) {
|
||||
if (!pid) return;
|
||||
|
||||
client_pid_ = pid;
|
||||
|
||||
// Determine root fsid
|
||||
vfs_context_t ctx = vfs_context_create(nullptr);
|
||||
if (ctx) {
|
||||
vnode_t root = vfs_rootvnode();
|
||||
if (root) {
|
||||
root_fsid_ = GetVnodeIDForVnode(ctx, root).fsid;
|
||||
vnode_put(root);
|
||||
}
|
||||
vfs_context_rele(ctx);
|
||||
}
|
||||
|
||||
// Any decisions made while the daemon wasn't
|
||||
// connected should be cleared
|
||||
ClearCache();
|
||||
|
||||
failed_decision_queue_requests_ = 0;
|
||||
failed_log_queue_requests_ = 0;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DisconnectClient(bool itDied, pid_t pid) {
|
||||
if (client_pid_ == 0 || (pid > 0 && pid != client_pid_)) return;
|
||||
client_pid_ = 0;
|
||||
|
||||
// Ask santad to shutdown, in case it's running.
|
||||
if (!itDied) {
|
||||
auto message = new santa_message_t;
|
||||
message->action = ACTION_REQUEST_SHUTDOWN;
|
||||
PostToDecisionQueue(message);
|
||||
delete message;
|
||||
decision_dataqueue_->setNotificationPort(nullptr);
|
||||
} else {
|
||||
// If the client died, reset the data queues so when it reconnects
|
||||
// it doesn't get swamped straight away.
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->release();
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->release();
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
|
||||
// Reset the filter.
|
||||
// On startup the daemon will add prefixes as needed.
|
||||
FilemodPrefixFilterReset();
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::ClientConnected() const {
|
||||
if (client_pid_ <= 0) return false;
|
||||
auto p = proc_find(client_pid_);
|
||||
auto is_exiting = false;
|
||||
if (p) {
|
||||
is_exiting = proc_exiting(p);
|
||||
proc_rele(p);
|
||||
}
|
||||
return (client_pid_ > 0 && !is_exiting);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::SetLogPort(mach_port_t port) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
IOMemoryDescriptor *r = decision_dataqueue_->getMemoryDescriptor();
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return r;
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
IOMemoryDescriptor *r = log_dataqueue_->getMemoryDescriptor();
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return r;
|
||||
}
|
||||
|
||||
#pragma mark Listener Control
|
||||
|
||||
kern_return_t SantaDecisionManager::StartListener() {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
vnode_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
|
||||
#pragma clang diagnostic pop
|
||||
if (!vnode_listener_) return kIOReturnInternalError;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
fileop_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_FILEOP, fileop_scope_callback,
|
||||
reinterpret_cast<void *>(this));
|
||||
#pragma clang diagnostic pop
|
||||
if (!fileop_listener_) return kIOReturnInternalError;
|
||||
|
||||
LOGD("Listeners started.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
kern_return_t SantaDecisionManager::StopListener() {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
kauth_unlisten_scope(vnode_listener_);
|
||||
vnode_listener_ = nullptr;
|
||||
|
||||
kauth_unlisten_scope(fileop_listener_);
|
||||
fileop_listener_ = nullptr;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// Wait for any active invocations to finish before returning
|
||||
do {
|
||||
IOSleep(5);
|
||||
} while (listener_invocations_);
|
||||
|
||||
// Delete any cached decisions
|
||||
ClearCache();
|
||||
|
||||
LOGD("Listeners stopped.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
# pragma mark Monitoring PIDs
|
||||
|
||||
// Arguments that are passed to pid_monitor thread.
|
||||
typedef struct {
|
||||
pid_t pid; // process to monitor
|
||||
SantaDecisionManager *sdm; // reference to SantaDecisionManager
|
||||
} pid_monitor_info;
|
||||
|
||||
// Function executed in its own thread used to monitor a compiler process for
|
||||
// termination and then remove the process pid from cache of compiler pids.
|
||||
static void pid_monitor(void *param, __unused wait_result_t wait_result) {
|
||||
pid_monitor_info *info = (pid_monitor_info *)param;
|
||||
if (info && info->sdm) {
|
||||
uint32_t sleep_time = info->sdm->PidMonitorSleepTimeMilliseconds();
|
||||
while (!info->sdm->PidMonitorThreadsShouldExit()) {
|
||||
proc_t proc = proc_find(info->pid);
|
||||
if (!proc) break;
|
||||
proc_rele(proc);
|
||||
IOSleep(sleep_time);
|
||||
}
|
||||
info->sdm->ForgetCompilerPid(info->pid);
|
||||
info->sdm->DecrementPidMonitorThreadCount();
|
||||
}
|
||||
thread_terminate(current_thread());
|
||||
}
|
||||
|
||||
// TODO(nguyenphillip): Look at moving pid monitoring out of SDM entirely,
|
||||
// maybe by creating a dedicated class to do this that SDM could then query.
|
||||
void SantaDecisionManager::MonitorCompilerPidForExit(pid_t pid) {
|
||||
// Don't start any new threads if compiler_pid_set_ doesn't exist.
|
||||
if (!compiler_pid_set_) return;
|
||||
auto info = new pid_monitor_info;
|
||||
info->pid = pid;
|
||||
info->sdm = this;
|
||||
thread_t thread = THREAD_NULL;
|
||||
IncrementPidMonitorThreadCount();
|
||||
if (KERN_SUCCESS != kernel_thread_start(pid_monitor, (void *)info, &thread)) {
|
||||
LOGE("couldn't start pid monitor thread");
|
||||
DecrementPidMonitorThreadCount();
|
||||
}
|
||||
thread_deallocate(thread);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::ForgetCompilerPid(pid_t pid) {
|
||||
if (compiler_pid_set_) compiler_pid_set_->remove(pid);
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PidMonitorThreadsShouldExit() const {
|
||||
return compiler_pid_set_ == nullptr;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::StopPidMonitorThreads() {
|
||||
// Each pid_monitor thread checks for the existence of compiler_pid_set_.
|
||||
// As soon as they see that it's gone, they should terminate and decrement
|
||||
// SantaDecisionManager's pid_monitor_thread_count. When this count decreases
|
||||
// to zero all threads have finished.
|
||||
auto temp = compiler_pid_set_;
|
||||
compiler_pid_set_ = nullptr;
|
||||
delete temp;
|
||||
|
||||
// Sleep time between checks starts at 10 ms, but increases to 5 sec after
|
||||
// 10 sec have passed without the thread count dropping to 0.
|
||||
unsigned int sleep_time_milliseconds = 10;
|
||||
unsigned int total_wait_time = 0;
|
||||
|
||||
while (pid_monitor_thread_count_ > 0) {
|
||||
if (sleep_time_milliseconds == 10) {
|
||||
total_wait_time += sleep_time_milliseconds;
|
||||
if (total_wait_time >= 10000) {
|
||||
sleep_time_milliseconds = 5000;
|
||||
LOGD("Waited %d ms for pid monitor threads to quit, switching sleep"
|
||||
"time to %d ms", total_wait_time, sleep_time_milliseconds);
|
||||
}
|
||||
}
|
||||
IOSleep(sleep_time_milliseconds);
|
||||
}
|
||||
LOGD("Pid monitor threads stopped.");
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t SantaDecisionManager::PidMonitorSleepTimeMilliseconds() const {
|
||||
return kPidMonitorSleepTimeMilliseconds;
|
||||
}
|
||||
|
||||
#pragma mark Cache Management
|
||||
|
||||
/**
|
||||
Return the correct cache for a given identifier.
|
||||
|
||||
@param identifier The identifier
|
||||
@return SantaCache* The cache to use
|
||||
*/
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *SantaDecisionManager::CacheForIdentifier(
|
||||
const santa_vnode_id_t identifier) {
|
||||
return (identifier.fsid == root_fsid_) ? root_decision_cache_ : non_root_decision_cache_;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::AddToCache(
|
||||
santa_vnode_id_t identifier, santa_action_t decision, uint64_t microsecs) {
|
||||
auto decision_cache = CacheForIdentifier(identifier);
|
||||
|
||||
switch (decision) {
|
||||
case ACTION_REQUEST_BINARY:
|
||||
decision_cache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ACK:
|
||||
decision_cache->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
|
||||
((uint64_t)ACTION_REQUEST_BINARY << 56));
|
||||
break;
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
case ACTION_RESPOND_DENY: {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
if (!decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
|
||||
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
decision_cache->set(identifier, val, 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
wakeup((void *)identifier.unsafe_simple_id());
|
||||
}
|
||||
|
||||
void SantaDecisionManager::RemoveFromCache(santa_vnode_id_t identifier) {
|
||||
if (unlikely(identifier.fsid == 0 && identifier.fileid == 0)) return;
|
||||
CacheForIdentifier(identifier)->remove(identifier);
|
||||
wakeup((void *)identifier.unsafe_simple_id());
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::RootCacheCount() const {
|
||||
return root_decision_cache_->count();
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::NonRootCacheCount() const {
|
||||
return non_root_decision_cache_->count();
|
||||
}
|
||||
|
||||
void SantaDecisionManager::ClearCache(bool non_root_only) {
|
||||
if (!non_root_only) root_decision_cache_->clear();
|
||||
non_root_decision_cache_->clear();
|
||||
}
|
||||
|
||||
void SantaDecisionManager::CacheBucketCount(
|
||||
uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
|
||||
root_decision_cache_->bucket_counts(per_bucket_counts, array_size, start_bucket);
|
||||
}
|
||||
|
||||
#pragma mark Decision Fetching
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromCache(santa_vnode_id_t identifier) {
|
||||
auto result = ACTION_UNSET;
|
||||
uint64_t decision_time = 0;
|
||||
|
||||
auto decision_cache = CacheForIdentifier(identifier);
|
||||
|
||||
uint64_t cache_val = decision_cache->get(identifier);
|
||||
if (cache_val == 0) return result;
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
result = (santa_action_t)(cache_val >> 56);
|
||||
decision_time = (cache_val & ~(0xFF00000000000000));
|
||||
|
||||
if (RESPONSE_VALID(result)) {
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
|
||||
if (expiry_time < GetCurrentUptime()) {
|
||||
decision_cache->remove(identifier);
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
santa_message_t *message, santa_vnode_id_t identifier) {
|
||||
auto return_action = ACTION_UNSET;
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_sec_t secs = 0;
|
||||
clock_usec_t microsecs = 0;
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
uint64_t uptime = (secs * 1000000) + microsecs;
|
||||
#endif
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Add pending request to cache, to be replaced
|
||||
// by daemon with actual response.
|
||||
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
|
||||
|
||||
// Send request to daemon.
|
||||
if (!PostToDecisionQueue(message)) {
|
||||
LOGE("Failed to queue request for %s.", message->path);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
// Check the cache every kRequestLoopSleepMilliseconds. Break this loop and send the request
|
||||
// again if kRequestCacheChecks is reached. Don't break the loop if the daemon is working on the
|
||||
// request, indicated with ACTION_RESPOND_ACK.
|
||||
auto cache_check_count = 0;
|
||||
do {
|
||||
msleep((void *)message->vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
|
||||
return_action = GetFromCache(identifier);
|
||||
} while (ClientConnected() &&
|
||||
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
|
||||
|| (return_action == ACTION_RESPOND_ACK)));
|
||||
} while (!RESPONSE_VALID(return_action) && ClientConnected());
|
||||
|
||||
// If response is still not valid, the daemon exited
|
||||
if (!RESPONSE_VALID(return_action)) {
|
||||
LOGE("Daemon process did not respond correctly. Allowing executions "
|
||||
"until it comes back. Executable path: %s", message->path);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
LOGD("Decision time: %4lldms (%s)",
|
||||
(((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
|
||||
#endif
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const santa_vnode_id_t vnode_id) {
|
||||
while (true) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id);
|
||||
|
||||
// If item was in cache with a valid response, return it.
|
||||
// If item is in cache but hasn't received a response yet, sleep for a bit.
|
||||
// If item is not in cache, break out of loop to send request to daemon.
|
||||
if (RESPONSE_VALID(return_action)) {
|
||||
return return_action;
|
||||
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
|
||||
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
|
||||
// until AddToCache is called, indicating a response has arrived.
|
||||
msleep((void *)vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
int name_len = MAXPATHLEN;
|
||||
path[MAXPATHLEN - 1] = 0;
|
||||
|
||||
if (vn_getpath(vp, path, &name_len) == ENOSPC) {
|
||||
return ACTION_RESPOND_TOOLONG;
|
||||
}
|
||||
|
||||
auto message = NewMessage(cred);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
message->action = ACTION_REQUEST_BINARY;
|
||||
message->vnode_id = vnode_id;
|
||||
proc_name(message->ppid, message->pname, sizeof(message->pname));
|
||||
auto return_action = GetFromDaemon(message, vnode_id);
|
||||
delete message;
|
||||
return return_action;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (++failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d decision requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (failed_log_queue_requests_++ == 0) {
|
||||
LOGW("Dropping log queue messages");
|
||||
}
|
||||
} else {
|
||||
if (failed_log_queue_requests_ > 0) {
|
||||
failed_log_queue_requests_--;
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
#pragma mark Invocation Tracking & PID comparison
|
||||
|
||||
void SantaDecisionManager::IncrementListenerInvocations() {
|
||||
OSIncrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DecrementListenerInvocations() {
|
||||
OSDecrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::IncrementPidMonitorThreadCount() {
|
||||
OSIncrementAtomic(&pid_monitor_thread_count_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DecrementPidMonitorThreadCount() {
|
||||
OSDecrementAtomic(&pid_monitor_thread_count_);
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::IsCompilerProcess(pid_t pid) {
|
||||
for (;;) {
|
||||
// Find the parent pid.
|
||||
proc_t proc = proc_find(pid);
|
||||
if (!proc) return false;
|
||||
pid_t ppid = proc_ppid(proc);
|
||||
proc_rele(proc);
|
||||
// Quit if process is launchd or has no parent.
|
||||
if (ppid == 0 || pid == ppid) break;
|
||||
pid_t val = compiler_pid_set_->get(pid);
|
||||
// If pid was in compiler_pid_set_ then make sure that it has the same
|
||||
// parent pid as when it was set.
|
||||
if (val) return val == ppid;
|
||||
// If pid not in the set, then quit unless we want to check ancestors.
|
||||
if (!kCheckCompilerAncestors) break;
|
||||
pid = ppid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma mark Callbacks
|
||||
|
||||
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vfs_context_t ctx,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Get ID for the vnode
|
||||
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
if (vnode_id.fsid == 0 && vnode_id.fileid == 0) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Fetch decision
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id);
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
|
||||
auto proc = vfs_context_proc(ctx);
|
||||
if (proc) {
|
||||
pid_t pid = proc_pid(proc);
|
||||
pid_t ppid = proc_ppid(proc);
|
||||
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
|
||||
uint64_t val = ((uint64_t)pid << 32) | ((uint64_t)ppid & 0xFFFFFFFF);
|
||||
vnode_pid_map_->set(vnode_id, val);
|
||||
if (returnedAction == ACTION_RESPOND_ALLOW_COMPILER && ppid != 0) {
|
||||
// Do some additional bookkeeping for compilers:
|
||||
// We associate the pid with a compiler so that when we see it later
|
||||
// in the context of a KAUTH_FILEOP event, we'll recognize it.
|
||||
compiler_pid_set_->set(pid, ppid);
|
||||
// And start polling for the compiler process termination, so that we
|
||||
// can remove the pid from our cache of compiler pids.
|
||||
MonitorCompilerPidForExit(pid);
|
||||
}
|
||||
}
|
||||
return KAUTH_RESULT_ALLOW;
|
||||
}
|
||||
case ACTION_RESPOND_DENY:
|
||||
*errno = EPERM;
|
||||
return KAUTH_RESULT_DENY;
|
||||
case ACTION_RESPOND_TOOLONG:
|
||||
*errno = ENAMETOOLONG;
|
||||
return KAUTH_RESULT_DENY;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
// we don't break user's machines.
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (!ClientConnected()) return;
|
||||
|
||||
// KAUTH_FILEOP_CLOSE implies KAUTH_FILEOP_CLOSE_MODIFIED, so remove it from the cache.
|
||||
if (action == KAUTH_FILEOP_CLOSE) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
RemoveFromCache(GetVnodeIDForVnode(context, vp));
|
||||
vfs_context_rele(context);
|
||||
}
|
||||
|
||||
// Don't log santad fileops.
|
||||
if (proc_selfpid() == client_pid_) return;
|
||||
|
||||
if (vp && action == KAUTH_FILEOP_EXEC) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
auto vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
vfs_context_rele(context);
|
||||
|
||||
auto message = NewMessage(nullptr);
|
||||
message->vnode_id = vnode_id;
|
||||
message->action = ACTION_NOTIFY_EXEC;
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
|
||||
// The vnode scope gets posix_spawn pid and ppid properly. The fileop scope does not.
|
||||
// Get pid and ppid cached during vnode execution.
|
||||
uint64_t val = vnode_pid_map_->get(vnode_id);
|
||||
if (val) {
|
||||
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
|
||||
message->pid = (pid_t)(val >> 32);
|
||||
message->ppid = (pid_t)(val & ~0xFFFFFFFF00000000);
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
|
||||
// For transitive whitelisting decisions, we must check for KAUTH_FILEOP_CLOSE events from a
|
||||
// known compiler process. But we must also check for KAUTH_FILEOP_RENAME events because clang
|
||||
// under Xcode 9 will, if the output file already exists, write to a temp file, delete the
|
||||
// existing file, then rename the temp file, without ever closing it. So in this scenario,
|
||||
// the KAUTH_FILEOP_RENAME is the only chance we have of whitelisting the output.
|
||||
if (action == KAUTH_FILEOP_CLOSE || (action == KAUTH_FILEOP_RENAME && new_path)) {
|
||||
auto message = NewMessage(nullptr);
|
||||
if (IsCompilerProcess(message->pid)) {
|
||||
// Fill out the rest of the message details and send it to the decision queue.
|
||||
auto context = vfs_context_create(nullptr);
|
||||
vnode_t real_vp = vp;
|
||||
// We have to manually look up the vnode pointer from new_path for KAUTH_FILEOP_RENAME.
|
||||
if (!real_vp && new_path && ERR_SUCCESS == vnode_lookup(new_path, 0, &real_vp, context)) {
|
||||
vnode_put(real_vp);
|
||||
}
|
||||
if (real_vp) message->vnode_id = GetVnodeIDForVnode(context, real_vp);
|
||||
vfs_context_rele(context);
|
||||
message->action = ACTION_NOTIFY_WHITELIST;
|
||||
const char *real_path = (action == KAUTH_FILEOP_CLOSE) ? path : new_path;
|
||||
strlcpy(message->path, real_path, sizeof(message->path));
|
||||
proc_name(message->pid, message->pname, sizeof(message->pname));
|
||||
PostToDecisionQueue(message);
|
||||
// Add a temporary allow rule to the decision cache for this vnode_id
|
||||
// while SNTCompilerController decides whether or not to add a
|
||||
// permanent rule for the new file to the rules database. This is
|
||||
// because checking if the file is a Mach-O binary and hashing it might
|
||||
// not finish before an attempt to execute it.
|
||||
AddToCache(message->vnode_id, ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE, 0);
|
||||
}
|
||||
delete message;
|
||||
// Don't need to do anything else for FILEOP_CLOSE, but FILEOP_RENAME should fall through.
|
||||
if (action == KAUTH_FILEOP_CLOSE) return;
|
||||
}
|
||||
|
||||
// Filter out modifications to locations that are definitely
|
||||
// not useful or made by santad.
|
||||
if (!filemod_prefix_filter_->HasPrefix(path)) {
|
||||
auto message = NewMessage(nullptr);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
|
||||
proc_name(message->pid, message->pname, sizeof(message->pname));
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_WRITE:
|
||||
// This is actually a KAUTH_VNODE_WRITE_DATA event.
|
||||
message->action = ACTION_NOTIFY_WRITE;
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
message->action = ACTION_NOTIFY_RENAME;
|
||||
break;
|
||||
case KAUTH_FILEOP_LINK:
|
||||
message->action = ACTION_NOTIFY_LINK;
|
||||
break;
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
message->action = ACTION_NOTIFY_EXCHANGE;
|
||||
break;
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
message->action = ACTION_NOTIFY_DELETE;
|
||||
break;
|
||||
default:
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
extern "C" int fileop_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (unlikely(sdm == nullptr)) {
|
||||
LOGE("fileop_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = nullptr;
|
||||
char *path = nullptr;
|
||||
char *new_path = nullptr;
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_CLOSE:
|
||||
// We only care about KAUTH_FILEOP_CLOSE events where the closed file
|
||||
// was modified.
|
||||
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED))
|
||||
return KAUTH_RESULT_DEFER;
|
||||
// Intentional fallthrough to get vnode reference.
|
||||
[[fallthrough]];
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
case KAUTH_FILEOP_EXEC:
|
||||
vp = reinterpret_cast<vnode_t>(arg0);
|
||||
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
case KAUTH_FILEOP_LINK:
|
||||
path = reinterpret_cast<char *>(arg0);
|
||||
new_path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
default:
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
sdm->IncrementListenerInvocations();
|
||||
sdm->FileOpCallback(action, vp, path, new_path);
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (unlikely(sdm == nullptr)) {
|
||||
LOGE("vnode_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
|
||||
|
||||
// We only care about regular files.
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
if ((action & (int)KAUTH_VNODE_EXECUTE) && !(action & (int)KAUTH_VNODE_ACCESS)) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
vp,
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
} else if (action & (int)KAUTH_VNODE_WRITE_DATA || action & (int)KAUTH_VNODE_APPEND_DATA) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
if (!(action & (int)KAUTH_VNODE_ACCESS)) {
|
||||
auto vnode_id = sdm->GetVnodeIDForVnode(reinterpret_cast<vfs_context_t>(arg0), vp);
|
||||
sdm->RemoveFromCache(vnode_id);
|
||||
}
|
||||
char path[MAXPATHLEN];
|
||||
int pathlen = MAXPATHLEN;
|
||||
vn_getpath(vp, path, &pathlen);
|
||||
// KAUTH_VNODE_WRITE_DATA events are translated into fake KAUTH_FILEOP_WRITE
|
||||
// events so that we can handle them in the FileOpCallback function.
|
||||
sdm->FileOpCallback(KAUTH_FILEOP_WRITE, vp, path, nullptr);
|
||||
sdm->DecrementListenerInvocations();
|
||||
}
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
#define SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
|
||||
#include <IOKit/IODataQueueShared.h>
|
||||
#include <IOKit/IOLib.h>
|
||||
#include <IOKit/IOMemoryDescriptor.h>
|
||||
#include <IOKit/IOSharedDataQueue.h>
|
||||
#include <sys/kauth.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
|
||||
///
|
||||
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
|
||||
/// and responding to the request appropriately.
|
||||
///
|
||||
/// Documentation on the Kauth parts can be found here:
|
||||
/// https://developer.apple.com/library/mac/technotes/tn2127/_index.html
|
||||
///
|
||||
class SantaDecisionManager : public OSObject {
|
||||
OSDeclareDefaultStructors(SantaDecisionManager);
|
||||
|
||||
public:
|
||||
/// Used for initialization after instantiation.
|
||||
bool init() override;
|
||||
|
||||
/// Called automatically when retain count drops to 0.
|
||||
void free() override;
|
||||
|
||||
/**
|
||||
Called by SantaDriverClient during connection to provide the shared
|
||||
dataqueue memory to the client for the decision queue.
|
||||
*/
|
||||
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
|
||||
|
||||
/**
|
||||
Called by SantaDriverClient during connection to provide the shared
|
||||
dataqueue memory to the client for the logging queue.
|
||||
*/
|
||||
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
|
||||
|
||||
/**
|
||||
Called by SantaDriverClient when a client connects to the decision queue,
|
||||
providing the pid of the client process.
|
||||
*/
|
||||
void ConnectClient(pid_t pid);
|
||||
|
||||
/// Called by SantaDriverClient when a client disconnects
|
||||
void DisconnectClient(bool itDied = false, pid_t pid = proc_selfpid());
|
||||
|
||||
/// Returns whether a client is currently connected or not.
|
||||
bool ClientConnected() const;
|
||||
|
||||
/// Sets the Mach port for notifying the decision queue.
|
||||
void SetDecisionPort(mach_port_t port);
|
||||
|
||||
/// Sets the Mach port for notifying the log queue.
|
||||
void SetLogPort(mach_port_t port);
|
||||
|
||||
/// Starts the kauth listeners.
|
||||
kern_return_t StartListener();
|
||||
|
||||
/**
|
||||
Stops the kauth listeners. After stopping new callback requests, waits
|
||||
until all current invocations have finished before clearing the cache and
|
||||
returning.
|
||||
*/
|
||||
kern_return_t StopListener();
|
||||
|
||||
/**
|
||||
This spins off a new thread for each process that we monitor. Generally the
|
||||
threads should be short-lived, since they die as soon as their associated
|
||||
compiler process dies.
|
||||
*/
|
||||
void MonitorCompilerPidForExit(pid_t pid);
|
||||
|
||||
/// Remove the given pid from cache of compiler pids.
|
||||
void ForgetCompilerPid(pid_t pid);
|
||||
|
||||
/// Returns true when SantaDecisionManager wants monitor threads to exit.
|
||||
bool PidMonitorThreadsShouldExit() const;
|
||||
|
||||
/**
|
||||
Stops the pid monitor threads. Waits until all threads have stopped before
|
||||
returning. This also frees the compiler_pid_set_. Returns true if all
|
||||
threads exited cleanly. Returns false if timed out while waiting.
|
||||
*/
|
||||
bool StopPidMonitorThreads();
|
||||
|
||||
/// Returns how long pid monitor should sleep between termination checks.
|
||||
uint32_t PidMonitorSleepTimeMilliseconds() const;
|
||||
|
||||
/// Adds a decision to the cache, with a timestamp.
|
||||
void AddToCache(santa_vnode_id_t identifier, const santa_action_t decision,
|
||||
const uint64_t microsecs = GetCurrentUptime());
|
||||
|
||||
/**
|
||||
Fetches a response from the cache, first checking to see if the entry
|
||||
has expired.
|
||||
*/
|
||||
santa_action_t GetFromCache(santa_vnode_id_t identifier);
|
||||
|
||||
/// Checks to see if a given identifier is in the cache and removes it.
|
||||
void RemoveFromCache(santa_vnode_id_t identifier);
|
||||
|
||||
/// Returns the number of entries in the cache.
|
||||
uint64_t RootCacheCount() const;
|
||||
uint64_t NonRootCacheCount() const;
|
||||
|
||||
/**
|
||||
Clears the cache(s). If non_root_only is true, only the non-root cache
|
||||
is cleared.
|
||||
*/
|
||||
void ClearCache(bool non_root_only = false);
|
||||
|
||||
/**
|
||||
Fills out the per_bucket_counts array with the number of items in each
|
||||
bucket in the non-root decision cache.
|
||||
|
||||
@param per_bucket_counts An array of uint16_t's to fill in with the number
|
||||
of items in each bucket. The size of this array is expected to equal
|
||||
array_size.
|
||||
@param array_size The size of the per_bucket_counts array on input. Upon
|
||||
return this will be updated to the number of slots that were actually used.
|
||||
@param start_bucket If non-zero this is the bucket in the cache to start
|
||||
from. Upon return this will be the next numbered bucket to start from for
|
||||
subsequent requests.
|
||||
*/
|
||||
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size,
|
||||
uint64_t *start_bucket);
|
||||
|
||||
/// Increments the count of active callbacks pending.
|
||||
void IncrementListenerInvocations();
|
||||
|
||||
/// Decrements the count of active callbacks pending.
|
||||
void DecrementListenerInvocations();
|
||||
|
||||
/// Increments the count of active pid monitor threads.
|
||||
void IncrementPidMonitorThreadCount();
|
||||
|
||||
/// Decrements the count of active pid monitor threads.
|
||||
void DecrementPidMonitorThreadCount();
|
||||
|
||||
/**
|
||||
Determine if pid belongs to a compiler process. When
|
||||
kCheckCompilerAncestors is set to true, this also checks all ancestor
|
||||
processes of the pid.
|
||||
*/
|
||||
bool IsCompilerProcess(pid_t pid);
|
||||
|
||||
/**
|
||||
Add a file modification prefix filter.
|
||||
*/
|
||||
inline IOReturn FilemodPrefixFilterAdd(const char *prefix,
|
||||
uint64_t *node_count = nullptr) {
|
||||
return filemod_prefix_filter_->AddPrefix(prefix, node_count);
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the file modification prefix filter tree.
|
||||
*/
|
||||
inline void FilemodPrefixFilterReset() { filemod_prefix_filter_->Reset(); }
|
||||
|
||||
/**
|
||||
Fetches the vnode_id for a given vnode.
|
||||
|
||||
@param ctx The VFS context to use.
|
||||
@param vp The Vnode to get the ID for
|
||||
@return santa_vnode_id_t The Vnode ID.
|
||||
*/
|
||||
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx,
|
||||
const vnode_t vp) {
|
||||
struct vnode_attr vap;
|
||||
VATTR_INIT(&vap);
|
||||
VATTR_WANTED(&vap, va_fsid);
|
||||
VATTR_WANTED(&vap, va_fileid);
|
||||
vnode_getattr(vp, &vap, ctx);
|
||||
return {.fsid = vap.va_fsid, .fileid = vap.va_fileid};
|
||||
}
|
||||
|
||||
/**
|
||||
Vnode Callback
|
||||
|
||||
@param cred The kauth credential for this request.
|
||||
@param ctx The VFS context for this request.
|
||||
@param vp The Vnode for this request.
|
||||
@param errno A pointer to return an errno style error.
|
||||
@return int A valid KAUTH_RESULT_*.
|
||||
*/
|
||||
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
|
||||
const vnode_t vp, int *errno);
|
||||
/**
|
||||
FileOp Callback
|
||||
|
||||
@param action The performed action
|
||||
@param vp The Vnode for this request. May be nullptr.
|
||||
@param path The path being operated on.
|
||||
@param new_path The target path for moves and links.
|
||||
*/
|
||||
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path,
|
||||
const char *new_path);
|
||||
|
||||
private:
|
||||
/**
|
||||
While waiting for a response from the daemon, this is the maximum number of
|
||||
milliseconds to sleep for before checking the cache for a response.
|
||||
*/
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
|
||||
|
||||
/**
|
||||
While waiting for a response from the daemon, this is the maximum number
|
||||
cache checks before re-sending the request.
|
||||
*/
|
||||
static const uint32_t kRequestCacheChecks = 5;
|
||||
|
||||
/**
|
||||
The maximum number of milliseconds a cached deny message should be
|
||||
considered valid.
|
||||
*/
|
||||
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
static const uint32_t kMaxCacheSize = 10000;
|
||||
|
||||
/// Maximum number of PostToDecisionQueue failures to allow.
|
||||
static const uint32_t kMaxDecisionQueueFailures = 10;
|
||||
|
||||
/**
|
||||
The maximum number of messages that can be kept in the decision data queue
|
||||
at any time.
|
||||
*/
|
||||
static const uint32_t kMaxDecisionQueueEvents = 512;
|
||||
|
||||
/**
|
||||
The maximum number of messages that can be kept in the logging data queue
|
||||
at any time.
|
||||
*/
|
||||
static const uint32_t kMaxLogQueueEvents = 2048;
|
||||
|
||||
/// How long pid monitor thread should sleep between termination checks.
|
||||
static const uint32_t kPidMonitorSleepTimeMilliseconds = 1000;
|
||||
|
||||
/**
|
||||
When set to true, Santa will check all ancestors of a process to determine
|
||||
if it is a compiler.
|
||||
TODO(nguyenphillip): this setting (and others above) should be configurable.
|
||||
*/
|
||||
static const bool kCheckCompilerAncestors = false;
|
||||
|
||||
/**
|
||||
Fetches a response from the daemon. Handles both daemon death
|
||||
and failure to post messages to the daemon.
|
||||
|
||||
@param message The message to send to the daemon
|
||||
@param identifier The vnode ID string for this request
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t GetFromDaemon(santa_message_t *message,
|
||||
santa_vnode_id_t identifier);
|
||||
|
||||
/**
|
||||
Fetches an execution decision for a file, first using the cache and then
|
||||
by sending a message to the daemon and waiting until a response arrives.
|
||||
If a daemon isn't connected, will allow execution and cache, logging
|
||||
the path to the executed file.
|
||||
|
||||
@param cred The credential for this request.
|
||||
@param vp The Vnode for this request.
|
||||
@param vnode_id The ID for this vnode.
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t FetchDecision(const kauth_cred_t cred, const vnode_t vp,
|
||||
const santa_vnode_id_t vnode_id);
|
||||
|
||||
/**
|
||||
Posts the requested message to the decision data queue.
|
||||
|
||||
@param message The message to send
|
||||
@return bool true if sending was successful.
|
||||
*/
|
||||
bool PostToDecisionQueue(santa_message_t *message);
|
||||
|
||||
/**
|
||||
Posts the requested message to the logging data queue.
|
||||
|
||||
@param message The message to send
|
||||
@return bool true if sending was successful.
|
||||
*/
|
||||
bool PostToLogQueue(santa_message_t *message);
|
||||
|
||||
/**
|
||||
Creates a new santa_message_t with some fields pre-filled.
|
||||
|
||||
@param credential The kauth_cred_t for this action, if available.
|
||||
If nullptr, will get the credential for the current process.
|
||||
*/
|
||||
static inline santa_message_t *NewMessage(kauth_cred_t credential) {
|
||||
bool should_release = false;
|
||||
if (credential == nullptr) {
|
||||
credential = kauth_cred_get_with_ref();
|
||||
should_release = true;
|
||||
}
|
||||
|
||||
auto message = new santa_message_t;
|
||||
message->uid = kauth_cred_getuid(credential);
|
||||
message->gid = kauth_cred_getgid(credential);
|
||||
message->pid = proc_selfpid();
|
||||
message->ppid = proc_selfppid();
|
||||
|
||||
if (should_release) {
|
||||
kauth_cred_unref(&credential);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current system uptime in microseconds
|
||||
*/
|
||||
static inline uint64_t GetCurrentUptime() {
|
||||
clock_sec_t sec;
|
||||
clock_usec_t usec;
|
||||
clock_get_system_microtime(&sec, &usec);
|
||||
return (uint64_t)((sec * 1000000) + usec);
|
||||
}
|
||||
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *root_decision_cache_;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *non_root_decision_cache_;
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;
|
||||
SantaCache<pid_t, pid_t> *compiler_pid_set_;
|
||||
|
||||
SNTPrefixTree *filemod_prefix_filter_;
|
||||
|
||||
/**
|
||||
Return the correct cache for a given identifier.
|
||||
|
||||
@param identifier The identifier
|
||||
@return SantaCache* The cache to use
|
||||
*/
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *CacheForIdentifier(
|
||||
const santa_vnode_id_t identifier);
|
||||
|
||||
// This is the file system ID of the root filesystem,
|
||||
// used to determine which cache to use for requests
|
||||
uint64_t root_fsid_;
|
||||
|
||||
lck_grp_t *sdm_lock_grp_;
|
||||
lck_grp_attr_t *sdm_lock_grp_attr_;
|
||||
lck_attr_t *sdm_lock_attr_;
|
||||
|
||||
lck_mtx_t *decision_dataqueue_lock_;
|
||||
lck_mtx_t *log_dataqueue_lock_;
|
||||
|
||||
IOSharedDataQueue *decision_dataqueue_;
|
||||
IOSharedDataQueue *log_dataqueue_;
|
||||
uint32_t failed_decision_queue_requests_;
|
||||
uint32_t failed_log_queue_requests_;
|
||||
|
||||
int32_t listener_invocations_;
|
||||
int32_t pid_monitor_thread_count_ = 0;
|
||||
|
||||
pid_t client_pid_;
|
||||
|
||||
kauth_listener_t vnode_listener_;
|
||||
kauth_listener_t fileop_listener_;
|
||||
|
||||
struct timespec ts_ = {
|
||||
.tv_sec = kRequestLoopSleepMilliseconds / 1000,
|
||||
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000};
|
||||
};
|
||||
|
||||
/**
|
||||
The kauth callback function for the Vnode scope
|
||||
|
||||
@param credential actor's credentials
|
||||
@param idata data that was passed when the listener was registered
|
||||
@param action action that was requested
|
||||
@param arg0 VFS context
|
||||
@param arg1 Vnode being operated on
|
||||
@param arg2 Parent Vnode. May be nullptr.
|
||||
@param arg3 Pointer to an errno-style error.
|
||||
*/
|
||||
extern "C" int vnode_scope_callback(kauth_cred_t credential, void *idata,
|
||||
kauth_action_t action, uintptr_t arg0,
|
||||
uintptr_t arg1, uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
/**
|
||||
The kauth callback function for the FileOp scope
|
||||
|
||||
@param credential actor's credentials
|
||||
@param idata data that was passed when the listener was registered
|
||||
@param action action that was requested
|
||||
@param arg0 depends on action, usually the vnode ref.
|
||||
@param arg1 depends on action.
|
||||
@param arg2 depends on action, usually 0.
|
||||
@param arg3 depends on action, usually 0.
|
||||
*/
|
||||
extern "C" int fileop_scope_callback(kauth_cred_t credential, void *idata,
|
||||
kauth_action_t action, uintptr_t arg0,
|
||||
uintptr_t arg1, uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
@@ -1,55 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/santa_driver/SantaDriver.h"
|
||||
|
||||
#define super IOService
|
||||
#define SantaDriver com_google_SantaDriver
|
||||
|
||||
// The defines above can'be used in this function, we must use the full names.
|
||||
OSDefineMetaClassAndStructors(com_google_SantaDriver, IOService);
|
||||
|
||||
bool SantaDriver::start(IOService *provider) {
|
||||
if (!super::start(provider)) return false;
|
||||
|
||||
santaDecisionManager = new SantaDecisionManager;
|
||||
if (!santaDecisionManager->init() ||
|
||||
santaDecisionManager->StartListener() != kIOReturnSuccess) {
|
||||
santaDecisionManager->release();
|
||||
santaDecisionManager = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
registerService();
|
||||
|
||||
LOGI("Loaded, version %s.", OSKextGetCurrentVersionString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDriver::stop(IOService *provider) {
|
||||
santaDecisionManager->StopListener();
|
||||
santaDecisionManager->release();
|
||||
santaDecisionManager = nullptr;
|
||||
|
||||
LOGI("Unloaded.");
|
||||
|
||||
super::stop(provider);
|
||||
}
|
||||
|
||||
SantaDecisionManager *SantaDriver::GetDecisionManager() const {
|
||||
return santaDecisionManager;
|
||||
}
|
||||
|
||||
#undef super
|
||||
@@ -1,46 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTADRIVER_H
|
||||
#define SANTA__SANTA_DRIVER__SANTADRIVER_H
|
||||
|
||||
#include <IOKit/IOService.h>
|
||||
#include <libkern/OSKextLib.h>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/santa_driver/SantaDecisionManager.h"
|
||||
|
||||
///
|
||||
/// The driver class, which provides the start/stop functions and holds
|
||||
/// the SantaDecisionManager instance which the connected client
|
||||
/// communicates with.
|
||||
///
|
||||
class com_google_SantaDriver : public IOService {
|
||||
OSDeclareDefaultStructors(com_google_SantaDriver);
|
||||
|
||||
public:
|
||||
/// Called by the kernel when the kext is loaded
|
||||
bool start(IOService *provider) override;
|
||||
|
||||
/// Called by the kernel when the kext is unloaded
|
||||
void stop(IOService *provider) override;
|
||||
|
||||
/// Returns a pointer to the SantaDecisionManager created in start().
|
||||
SantaDecisionManager *GetDecisionManager() const;
|
||||
|
||||
private:
|
||||
SantaDecisionManager *santaDecisionManager;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADRIVER_H
|
||||
@@ -1,305 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/santa_driver/SantaDriverClient.h"
|
||||
|
||||
#define super IOUserClient
|
||||
#define SantaDriverClient com_google_SantaDriverClient
|
||||
|
||||
// The defines above can'be used in this function, must use the full names.
|
||||
OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient);
|
||||
|
||||
#pragma mark Driver Management
|
||||
|
||||
bool SantaDriverClient::initWithTask(
|
||||
task_t owningTask, void *securityID, UInt32 type) {
|
||||
if (clientHasPrivilege(
|
||||
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
|
||||
LOGW("Unprivileged client attempted to connect.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!super::initWithTask(owningTask, securityID, type)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SantaDriverClient::start(IOService *provider) {
|
||||
myProvider = OSDynamicCast(com_google_SantaDriver, provider);
|
||||
if (!myProvider) return false;
|
||||
|
||||
decisionManager = myProvider->GetDecisionManager();
|
||||
if (!decisionManager) return false;
|
||||
decisionManager->retain();
|
||||
|
||||
return super::start(provider);
|
||||
}
|
||||
|
||||
void SantaDriverClient::stop(IOService *provider) {
|
||||
myProvider = nullptr;
|
||||
decisionManager->release();
|
||||
decisionManager = nullptr;
|
||||
super::stop(provider);
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientDied() {
|
||||
LOGI("Client died.");
|
||||
decisionManager->DisconnectClient(true);
|
||||
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientClose() {
|
||||
LOGI("Client disconnected.");
|
||||
decisionManager->DisconnectClient();
|
||||
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
|
||||
}
|
||||
|
||||
bool SantaDriverClient::didTerminate(IOService *provider, IOOptionBits options, bool *defer) {
|
||||
decisionManager->DisconnectClient(false, 0);
|
||||
if (myProvider && myProvider->isOpen(this)) myProvider->close(this);
|
||||
return super::didTerminate(provider, options, defer);
|
||||
}
|
||||
|
||||
#pragma mark Fetching memory and data queue notifications
|
||||
|
||||
IOReturn SantaDriverClient::registerNotificationPort(
|
||||
mach_port_t port, UInt32 type, UInt32 ref) {
|
||||
if (port == MACH_PORT_NULL) return kIOReturnError;
|
||||
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION:
|
||||
decisionManager->SetDecisionPort(port);
|
||||
break;
|
||||
case QUEUETYPE_LOG:
|
||||
decisionManager->SetLogPort(port);
|
||||
break;
|
||||
default:
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientMemoryForType(
|
||||
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) {
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION:
|
||||
*options = 0;
|
||||
*memory = decisionManager->GetDecisionMemoryDescriptor();
|
||||
decisionManager->ConnectClient(proc_selfpid());
|
||||
break;
|
||||
case QUEUETYPE_LOG:
|
||||
*options = 0;
|
||||
*memory = decisionManager->GetLogMemoryDescriptor();
|
||||
break;
|
||||
default:
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
(*memory)->retain();
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
#pragma mark Callable Methods
|
||||
|
||||
IOReturn SantaDriverClient::open(
|
||||
OSObject *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (me->isInactive()) return kIOReturnNotAttached;
|
||||
if (!me->myProvider->open(me)) {
|
||||
LOGW("A second client tried to connect.");
|
||||
return kIOReturnExclusiveAccess;
|
||||
}
|
||||
|
||||
LOGI("Client connected.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::allow_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::allow_compiler(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW_COMPILER);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::deny_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_DENY);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::acknowledge_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ACK);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clear_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
const bool non_root_only = static_cast<const bool>(arguments->scalarInput[0]);
|
||||
me->decisionManager->ClearCache(non_root_only);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::remove_cache_entry(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
me->decisionManager->RemoveFromCache(*vnode_id);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::cache_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
arguments->scalarOutput[0] = me->decisionManager->RootCacheCount();
|
||||
arguments->scalarOutput[1] = me->decisionManager->NonRootCacheCount();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::check_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
|
||||
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
|
||||
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
|
||||
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(*vnode_id);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::cache_bucket_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
santa_bucket_count_t *counts = reinterpret_cast<santa_bucket_count_t *>(
|
||||
arguments->structureOutput);
|
||||
const santa_bucket_count_t *input = reinterpret_cast<const santa_bucket_count_t *>(
|
||||
arguments->structureInput);
|
||||
|
||||
uint16_t s = sizeof(counts->per_bucket) / sizeof(uint16_t);
|
||||
counts->start = input->start;
|
||||
me->decisionManager->CacheBucketCount(counts->per_bucket, &s, &(counts->start));
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::filemod_prefix_filter_add(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
const char *prefix = reinterpret_cast<const char *>(arguments->structureInput);
|
||||
return me->decisionManager->FilemodPrefixFilterAdd(prefix, arguments->scalarOutput);
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::filemod_prefix_filter_reset(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
me->decisionManager->FilemodPrefixFilterReset();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
#pragma mark Method Resolution
|
||||
|
||||
IOReturn SantaDriverClient::externalMethod(
|
||||
UInt32 selector,
|
||||
IOExternalMethodArguments *arguments,
|
||||
IOExternalMethodDispatch *dispatch,
|
||||
OSObject *target,
|
||||
void *reference) {
|
||||
/// Array of methods callable by clients. The order of these must match the
|
||||
/// order of the items in SantaDriverMethods in SNTKernelCommon.h
|
||||
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
|
||||
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
|
||||
{ &SantaDriverClient::open, 0, 0, 0, 0 },
|
||||
{ &SantaDriverClient::allow_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::allow_compiler, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::deny_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::acknowledge_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
|
||||
{ &SantaDriverClient::remove_cache_entry, 0, sizeof(santa_vnode_id_t), 0, 0 },
|
||||
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
|
||||
{ &SantaDriverClient::check_cache, 0, sizeof(santa_vnode_id_t), 1, 0 },
|
||||
{ &SantaDriverClient::cache_bucket_count, 0, sizeof(santa_bucket_count_t),
|
||||
0, sizeof(santa_bucket_count_t) },
|
||||
{ &SantaDriverClient::filemod_prefix_filter_add, 0, sizeof(const char[MAXPATHLEN]), 1, 0 },
|
||||
{ &SantaDriverClient::filemod_prefix_filter_reset, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
if (selector >= static_cast<UInt32>(kSantaUserClientNMethods)) {
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
dispatch = &(sMethods[selector]);
|
||||
if (!target) target = this;
|
||||
return super::externalMethod(selector, arguments, dispatch, target, reference);
|
||||
}
|
||||
|
||||
#undef super
|
||||
@@ -1,136 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
#define SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
|
||||
#include <IOKit/IOUserClient.h>
|
||||
#include <sys/kauth.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/santa_driver/SantaDecisionManager.h"
|
||||
#include "Source/santa_driver/SantaDriver.h"
|
||||
|
||||
///
|
||||
/// This class is instantiated by IOKit when a new client process attempts to
|
||||
/// connect to the driver. Starting, stopping, handling connections, allocating
|
||||
/// shared memory and establishing a data queue is handled here.
|
||||
///
|
||||
/// Documentation on how the IOUserClient parts of this code work can be found
|
||||
/// here:
|
||||
/// https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html
|
||||
/// https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingDeviceDriver/WritingDeviceDriver.pdf
|
||||
///
|
||||
class com_google_SantaDriverClient : public IOUserClient {
|
||||
OSDeclareDefaultStructors(com_google_SantaDriverClient);
|
||||
|
||||
public:
|
||||
/// Called as part of IOServiceOpen in clients
|
||||
bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;
|
||||
|
||||
/// Called after initWithTask as part of IOServiceOpen
|
||||
bool start(IOService *provider) override;
|
||||
|
||||
/// Called when this class is stopping
|
||||
void stop(IOService *provider) override;
|
||||
|
||||
/// Called when a client manually disconnects (via IOServiceClose)
|
||||
IOReturn clientClose() override;
|
||||
|
||||
/// Called when a client dies
|
||||
IOReturn clientDied() override;
|
||||
|
||||
/// Called during termination
|
||||
bool didTerminate(IOService *provider, IOOptionBits options,
|
||||
bool *defer) override;
|
||||
|
||||
/// Called in clients with IOConnectSetNotificationPort
|
||||
IOReturn registerNotificationPort(mach_port_t port, UInt32 type,
|
||||
UInt32 refCon) override;
|
||||
|
||||
/// Called in clients with IOConnectMapMemory
|
||||
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options,
|
||||
IOMemoryDescriptor **memory) override;
|
||||
|
||||
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
|
||||
/// to the requested selector using the SantaDriverMethods enum in
|
||||
/// SNTKernelCommon.
|
||||
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments,
|
||||
IOExternalMethodDispatch *dispatch, OSObject *target,
|
||||
void *reference) override;
|
||||
|
||||
///
|
||||
/// The userspace callable methods are below. Each method corresponds
|
||||
/// to an entry in SantaDriverMethods.
|
||||
///
|
||||
|
||||
/// Called during client connection.
|
||||
static IOReturn open(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a binary.
|
||||
static IOReturn allow_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a compiler binary.
|
||||
static IOReturn allow_compiler(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to deny a binary.
|
||||
static IOReturn deny_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to acknowledge a binary request. This is used for
|
||||
/// large binaries that may take a while to reach a decision.
|
||||
static IOReturn acknowledge_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to empty the cache.
|
||||
static IOReturn clear_cache(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon call this to remove a single cache entry.
|
||||
static IOReturn remove_cache_entry(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in the cache
|
||||
static IOReturn cache_count(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out the status of a vnode_id in the cache.
|
||||
/// Output will be a santa_action_t.
|
||||
static IOReturn check_cache(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in each cache
|
||||
/// bucket. Input and output are both an instance of santa_bucket_count_t.
|
||||
static IOReturn cache_bucket_count(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to add filemod prefix filters at daemon startup.
|
||||
static IOReturn filemod_prefix_filter_add(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to reset the prefix filter tree.
|
||||
static IOReturn filemod_prefix_filter_reset(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
private:
|
||||
com_google_SantaDriver *myProvider;
|
||||
SantaDecisionManager *decisionManager;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
@@ -1,878 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <IOKit/IODataQueueClient.h>
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#import <IOKit/kext/KextManager.h>
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#include <libkern/OSKextLib.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
|
||||
///
|
||||
/// Kernel Extension Tests
|
||||
///
|
||||
/// Build and launch as root. This target is dependent on the santa-driver target and these
|
||||
/// tests will load santa-driver from the same location this binary is executed from, unloading
|
||||
/// any existing driver (and daemon) if necessary.
|
||||
///
|
||||
|
||||
#define TSTART(testName) \
|
||||
do { \
|
||||
printf(" %-50s ", testName); \
|
||||
} while (0)
|
||||
#define TPASS() \
|
||||
do { \
|
||||
printf("PASS\n"); \
|
||||
} while (0)
|
||||
#define TPASSINFO(fmt, ...) \
|
||||
do { \
|
||||
printf("PASS\n " fmt "\n", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#define TFAIL() \
|
||||
do { \
|
||||
printf("FAIL\n"); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
#define TFAILINFO(fmt, ...) \
|
||||
do { \
|
||||
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
@interface SantaKernelTests : NSObject
|
||||
@property io_connect_t connection;
|
||||
|
||||
// A block that tests can set to handle specific files/binaries.
|
||||
// The block should return an action to respond to the kernel with.
|
||||
// If no block is specified or no action is returned, the exec will be allowed.
|
||||
@property(atomic, copy) santa_action_t (^handlerBlock)(santa_message_t msg);
|
||||
|
||||
- (void)unloadDaemon;
|
||||
- (void)unloadExtension;
|
||||
- (void)loadExtension;
|
||||
- (void)runTests;
|
||||
@end
|
||||
|
||||
@implementation SantaKernelTests
|
||||
|
||||
#pragma mark - Test Helpers
|
||||
|
||||
/// Return an initialized NSTask for |path| with stdout, stdin and stderr directed to /dev/null
|
||||
- (NSTask *)taskWithPath:(NSString *)path {
|
||||
NSTask *t = [[NSTask alloc] init];
|
||||
t.launchPath = path;
|
||||
t.standardInput = nil;
|
||||
t.standardOutput = nil;
|
||||
t.standardError = nil;
|
||||
return t;
|
||||
}
|
||||
|
||||
- (NSString *)sha256ForPath:(NSString *)path {
|
||||
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
|
||||
NSData *fData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:nil];
|
||||
CC_SHA256([fData bytes], (unsigned int)[fData length], sha256);
|
||||
char buf[CC_SHA256_DIGEST_LENGTH * 2 + 1];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
|
||||
snprintf(buf + (2 * i), 4, "%02x", (unsigned char)sha256[i]);
|
||||
}
|
||||
buf[CC_SHA256_DIGEST_LENGTH * 2] = '\0';
|
||||
return @(buf);
|
||||
}
|
||||
|
||||
/// Return the path to the version of ld being used by clang.
|
||||
- (NSString *)ldPath {
|
||||
static NSString *path;
|
||||
if (!path) {
|
||||
NSTask *xcrun = [self taskWithPath:@"/usr/bin/xcrun"];
|
||||
xcrun.arguments = @[ @"-f", @"ld" ];
|
||||
xcrun.standardOutput = [NSPipe pipe];
|
||||
@try {
|
||||
[xcrun launch];
|
||||
[xcrun waitUntilExit];
|
||||
} @catch (NSException *exception) {
|
||||
return nil;
|
||||
}
|
||||
if (xcrun.terminationStatus != 0) return nil;
|
||||
NSData *data = [[xcrun.standardOutput fileHandleForReading] readDataToEndOfFile];
|
||||
if (!data) return nil;
|
||||
path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
|
||||
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
#pragma mark - Driver Helpers
|
||||
|
||||
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
|
||||
/// passing the |vnodeID|.
|
||||
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeid {
|
||||
switch (action) {
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_DENY:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ACK:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
default:
|
||||
TFAILINFO("postToKernelAction:forVnodeID: received unknown action type: %d", action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Call in-kernel function: |kSantaUserClientClearCache|
|
||||
- (void)flushCache {
|
||||
uint64_t nonRootOnly = 0;
|
||||
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, &nonRootOnly, 1, 0, 0);
|
||||
}
|
||||
|
||||
#pragma mark - Connection Tests
|
||||
|
||||
/// Tests the process of locating, attaching and opening the driver. Also verifies that the
|
||||
/// driver correctly refuses non-privileged connections.
|
||||
- (void)connectionTests {
|
||||
kern_return_t kr;
|
||||
io_service_t serviceObject;
|
||||
CFDictionaryRef classToMatch;
|
||||
|
||||
TSTART("Creates matching service dictionary");
|
||||
if (!(classToMatch = IOServiceMatching(USERCLIENT_CLASS))) {
|
||||
TFAIL();
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Locates Santa driver");
|
||||
serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch);
|
||||
if (!serviceObject) {
|
||||
TFAILINFO("Is santa-driver.kext loaded?");
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Driver refuses non-privileged connections");
|
||||
(void)setegid(-2);
|
||||
(void)seteuid(-2);
|
||||
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
|
||||
if (kr != kIOReturnBadArgument) {
|
||||
TFAIL();
|
||||
}
|
||||
(void)setegid(0);
|
||||
(void)seteuid(0);
|
||||
TPASS();
|
||||
|
||||
TSTART("Attaches to and starts Santa service");
|
||||
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
|
||||
IOObjectRelease(serviceObject);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
TFAILINFO("KR: %d", kr);
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Calls 'open' method on driver");
|
||||
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
if (kr == kIOReturnExclusiveAccess) {
|
||||
TFAILINFO("A client is already connected to the driver.\n"
|
||||
"Please kill the existing client and re-run the test.");
|
||||
} else if (kr != kIOReturnSuccess) {
|
||||
TFAILINFO("KR: %d", kr);
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Refuses second client");
|
||||
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
if (kr != kIOReturnExclusiveAccess) {
|
||||
TFAIL();
|
||||
}
|
||||
TPASS();
|
||||
}
|
||||
|
||||
#pragma mark - Listener
|
||||
|
||||
/// Tests the process of allocating & registering a notification port and mapping shared memory.
|
||||
/// From then on, monitors the IODataQueue and responds for files specifically used in other tests.
|
||||
/// For everything else, allows execution normally to avoid deadlocking the system.
|
||||
- (void)beginListening {
|
||||
TSTART("Allocates a notification port");
|
||||
mach_port_t receivePort;
|
||||
if (!(receivePort = IODataQueueAllocateNotificationPort())) {
|
||||
TFAIL();
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Registers the notification port");
|
||||
kern_return_t kr =
|
||||
IOConnectSetNotificationPort(self.connection, QUEUETYPE_DECISION, receivePort, 0);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
TFAILINFO("KR: %d", kr);
|
||||
return;
|
||||
}
|
||||
TPASS();
|
||||
|
||||
TSTART("Maps shared memory");
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(), &address, &size,
|
||||
kIOMapAnywhere);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
TFAILINFO("KR: %d", kr);
|
||||
}
|
||||
TPASS();
|
||||
|
||||
/// Begin listening for events
|
||||
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
|
||||
do {
|
||||
while (IODataQueueDataAvailable(queueMemory)) {
|
||||
santa_message_t vdata;
|
||||
UInt32 dataSize = sizeof(vdata);
|
||||
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
TFAILINFO("Error receiving data: %d", kr);
|
||||
continue;
|
||||
}
|
||||
if (vdata.action != ACTION_REQUEST_BINARY) continue;
|
||||
|
||||
santa_action_t action = ACTION_RESPOND_ALLOW;
|
||||
|
||||
@synchronized(self) {
|
||||
if (self.handlerBlock) action = self.handlerBlock(vdata);
|
||||
}
|
||||
|
||||
[self postToKernelAction:action forVnodeID:vdata.vnode_id];
|
||||
}
|
||||
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
|
||||
|
||||
IOConnectUnmapMemory(self.connection, kIODefaultMemoryType, mach_task_self(), address);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
}
|
||||
|
||||
#pragma mark - Functional Tests
|
||||
|
||||
- (void)receiveAndBlockTests {
|
||||
TSTART("Blocks denied binaries");
|
||||
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/ed", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
NSTask *ed = [self taskWithPath:@"/bin/ed"];
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAIL();
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)receiveAndCacheTests {
|
||||
TSTART("Permits & caches allowed binaries");
|
||||
|
||||
__block int timesSeenLs = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/ls", msg.path, 7) == 0) ++timesSeenLs;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
NSTask *ls = [self taskWithPath:@"/bin/ls"];
|
||||
[ls launch];
|
||||
[ls waitUntilExit];
|
||||
|
||||
if (timesSeenLs != 1) {
|
||||
TFAILINFO("Didn't record first run of ls");
|
||||
}
|
||||
|
||||
ls = [self taskWithPath:@"/bin/ls"];
|
||||
[ls launch];
|
||||
[ls waitUntilExit];
|
||||
|
||||
if (timesSeenLs > 1) {
|
||||
TFAILINFO("Received request for ls a second time");
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)invalidatesCacheTests {
|
||||
TSTART("Invalidates cache for manually closed FDs");
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSString *target =
|
||||
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
__strong __typeof(weakSelf) self = weakSelf;
|
||||
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
// Copy the pwd binary to a new file
|
||||
if (![fm copyItemAtPath:@"/bin/pwd" toPath:target error:nil]) {
|
||||
TFAILINFO("Failed to create temp file");
|
||||
}
|
||||
|
||||
// Launch the new file to put it in the cache
|
||||
NSTask *pwd = [self taskWithPath:target];
|
||||
[pwd launch];
|
||||
[pwd waitUntilExit];
|
||||
|
||||
// Exit if this fails with a useful message.
|
||||
if ([pwd terminationStatus] != 0) {
|
||||
TFAILINFO("First launch of test binary failed");
|
||||
}
|
||||
|
||||
// Now replace the contents of the test file (which is cached) with the contents of /bin/ed,
|
||||
// which is blocked by SHA-256 during the tests.
|
||||
FILE *infile = fopen("/bin/ed", "r");
|
||||
FILE *outfile = fopen(target.UTF8String, "w");
|
||||
int ch;
|
||||
while ((ch = fgetc(infile)) != EOF) {
|
||||
fputc(ch, outfile);
|
||||
}
|
||||
fclose(infile);
|
||||
|
||||
// Now try running the temp file again. If it succeeds, the test failed.
|
||||
NSTask *ed = [self taskWithPath:target];
|
||||
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after write while file open");
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
} @catch (NSException *exception) {
|
||||
// This is a pass, but we have more to do.
|
||||
}
|
||||
|
||||
// Close the file to flush the write.
|
||||
fclose(outfile);
|
||||
|
||||
// And try running the temp file again. If it succeeds, the test failed.
|
||||
ed = [self taskWithPath:target];
|
||||
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after file closed");
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
} @finally {
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invalidatesCacheAutoCloseTest {
|
||||
TSTART("Invalidates cache for auto closed FDs");
|
||||
|
||||
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
__strong __typeof(weakSelf) self = weakSelf;
|
||||
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
// Create temporary file
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
|
||||
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
|
||||
TFAILINFO("Failed to create temp file");
|
||||
}
|
||||
|
||||
// Launch the new file to put it in the cache
|
||||
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
[pwd launch];
|
||||
[pwd waitUntilExit];
|
||||
if ([pwd terminationStatus] != 0) {
|
||||
TFAILINFO("Second launch of test binary failed");
|
||||
}
|
||||
|
||||
// Replace file contents using dd, which doesn't close FDs
|
||||
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
|
||||
NSTask *dd = [self taskWithPath:@"/bin/dd"];
|
||||
dd.arguments = @[
|
||||
@"if=/bin/ed", @"of=invalidacachetest_tmp", @"bs=1",
|
||||
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
|
||||
];
|
||||
[dd launch];
|
||||
[dd waitUntilExit];
|
||||
|
||||
// And try running the temp file again. If it succeeds, the test failed.
|
||||
NSTask *ed = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after file closed");
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
} @finally {
|
||||
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearCacheTests {
|
||||
TSTART("Can clear cache");
|
||||
|
||||
__block int timesSeenCat = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/cat", msg.path, 8) == 0) ++timesSeenCat;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
NSTask *cat = [self taskWithPath:@"/bin/cat"];
|
||||
[cat launch];
|
||||
[cat waitUntilExit];
|
||||
|
||||
if (timesSeenCat != 1) {
|
||||
TFAILINFO("Didn't record first run of cat");
|
||||
}
|
||||
|
||||
[self flushCache];
|
||||
|
||||
cat = [self taskWithPath:@"/bin/cat"];
|
||||
[cat launch];
|
||||
[cat waitUntilExit];
|
||||
|
||||
if (timesSeenCat != 2) {
|
||||
TFAIL();
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)blocksDeniedTracedBinaries {
|
||||
TSTART("Denies blocked processes running while traced");
|
||||
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (strncmp("/bin/mv", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
TFAILINFO("Failed to fork");
|
||||
} else if (pid > 0) {
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0) != pid)
|
||||
; // handle EINTR
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
|
||||
TPASS();
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
TFAILINFO("Process was executed and is waiting for debugger");
|
||||
} else {
|
||||
TFAILINFO("Process did not exit with EPERM as expected");
|
||||
}
|
||||
} else if (pid == 0) {
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
ptrace(PT_TRACE_ME, 0, 0, 0);
|
||||
execl("/bin/mv", "mv", NULL);
|
||||
_exit(errno);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testCachePerformance {
|
||||
TSTART("Test cache performance...");
|
||||
|
||||
// Execute echo 100 times, saving the time taken for each run
|
||||
std::vector<double> times;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
NSTask *t = [[NSTask alloc] init];
|
||||
t.launchPath = @"/bin/echo";
|
||||
t.standardOutput = [NSPipe pipe];
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
[t launch];
|
||||
[t waitUntilExit];
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
|
||||
if (i > 5) times.push_back(duration);
|
||||
}
|
||||
|
||||
// Sort and remove first 10 and last 10 entries.
|
||||
std::sort(times.begin(), times.end());
|
||||
times.erase(times.begin(), times.begin() + 10);
|
||||
times.erase(times.end() - 10, times.end());
|
||||
|
||||
// Calculate mean
|
||||
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
|
||||
|
||||
// Calculate stdev
|
||||
double accum = 0.0;
|
||||
std::for_each(times.begin(), times.end(),
|
||||
[&](const double d) { accum += (d - mean) * (d - mean); });
|
||||
double stdev = sqrt(accum / (times.size() - 1));
|
||||
|
||||
if (mean > 80 || stdev > 10) {
|
||||
TFAILINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
|
||||
} else {
|
||||
TPASSINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testLargeBinary {
|
||||
TSTART("Handles large binary...");
|
||||
|
||||
__block int calCount = 0;
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
__strong __typeof(weakSelf) self = weakSelf;
|
||||
if (strncmp("/usr/bin/cal", msg.path, 12) == 0) {
|
||||
if (calCount++) TFAILINFO("Large binary should not re-request");
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
|
||||
dispatch_get_global_queue(0, 0), ^{
|
||||
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
|
||||
});
|
||||
return ACTION_RESPOND_ACK;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
|
||||
@try {
|
||||
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
|
||||
[testexec launch];
|
||||
int sleepCount = 0;
|
||||
while ([testexec isRunning]) {
|
||||
sleep(1);
|
||||
if (++sleepCount > 5) TFAILINFO("Took longer than expected to start/stop");
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
TFAILINFO("Failed to launch");
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)testPendingTransitiveRules {
|
||||
TSTART("Adds pending transitive whitelist rules");
|
||||
|
||||
NSString *ldPath = [self ldPath];
|
||||
if (!ldPath) {
|
||||
TFAILINFO("Couldn't get path to ld");
|
||||
}
|
||||
|
||||
// Clear out cached decisions from any previous tests.
|
||||
[self flushCache];
|
||||
|
||||
__block int ldCount = 0;
|
||||
__block int helloCount = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (!strcmp(ldPath.UTF8String, msg.path)) {
|
||||
ldCount++;
|
||||
return ACTION_RESPOND_ALLOW_COMPILER;
|
||||
} else if (!strcmp("/private/tmp/hello", msg.path)) {
|
||||
helloCount++;
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
// Write source file to /tmp/hello.c
|
||||
FILE *out = fopen("/tmp/hello.c", "wb");
|
||||
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
|
||||
fclose(out);
|
||||
// Then compile it with clang and ld, the latter of which has been marked as a compiler.
|
||||
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
|
||||
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
|
||||
[clang launch];
|
||||
[clang waitUntilExit];
|
||||
// Make sure that our version of ld marked as compiler was run. This assumes that
|
||||
// "xcode-select -p" returns "/Applications/Xcode.app/Contents/Developer"
|
||||
if (ldCount != 1) {
|
||||
TFAILINFO("Didn't record run of ld");
|
||||
}
|
||||
// Check if we can now run /private/tmp/hello. If working correctly, there will already be
|
||||
// a pending transitive rule in the cache, so no decision request will be sent to listener.
|
||||
// If for some reason a decision request is sent, then binary will be denied.
|
||||
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
|
||||
@try {
|
||||
[hello launch];
|
||||
[hello waitUntilExit];
|
||||
} @catch (NSException *exception) {
|
||||
TFAILINFO("could not launch /private/tmp/hello: %s", exception.reason.UTF8String);
|
||||
}
|
||||
// Check that the listener was not consulted for the decision.
|
||||
if (helloCount > 0) {
|
||||
TFAILINFO("pending decision for /private/tmp/hello was not in cache");
|
||||
}
|
||||
|
||||
// Clean up
|
||||
remove("/tmp/hello");
|
||||
remove("/tmp/hello.c");
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)testNoTransitiveRules {
|
||||
TSTART("No transitive rule generated by non-compiler");
|
||||
|
||||
NSString *ldPath = [self ldPath];
|
||||
if (!ldPath) {
|
||||
TFAILINFO("Couldn't get path to ld");
|
||||
}
|
||||
|
||||
// Clear out cached decisions from any previous tests.
|
||||
[self flushCache];
|
||||
|
||||
__block int ldCount = 0;
|
||||
__block int helloCount = 0;
|
||||
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
|
||||
if (!strcmp(ldPath.UTF8String, msg.path)) {
|
||||
ldCount++;
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
} else if (!strcmp("/private/tmp/hello", msg.path)) {
|
||||
helloCount++;
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
};
|
||||
// Write source file to /tmp/hello.c
|
||||
FILE *out = fopen("/tmp/hello.c", "wb");
|
||||
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
|
||||
fclose(out);
|
||||
// Then compile it with clang and ld, neither of which have been marked as a compiler.
|
||||
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
|
||||
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
|
||||
@try {
|
||||
[clang launch];
|
||||
[clang waitUntilExit];
|
||||
} @catch (NSException *exception) {
|
||||
TFAILINFO("Couldn't launch clang");
|
||||
}
|
||||
// Make sure that our version of ld was run. This assumes that "xcode-select -p"
|
||||
// returns "/Applications/Xcode.app/Contents/Developer"
|
||||
if (ldCount != 1) {
|
||||
TFAILINFO("Didn't record run of ld");
|
||||
}
|
||||
// Check that we cannot run /private/tmp/hello.
|
||||
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
|
||||
@try {
|
||||
[hello launch];
|
||||
[hello waitUntilExit];
|
||||
TFAILINFO("Should not have been able to launch /private/tmp/hello");
|
||||
} @catch (NSException *exception) {
|
||||
// All good
|
||||
}
|
||||
// Check that there wasn't a decision for /private/tmp/hello in the cache.
|
||||
if (helloCount != 1) {
|
||||
TFAILINFO("decision for /private/tmp/hello found in cache");
|
||||
}
|
||||
|
||||
// Clean up
|
||||
remove("/tmp/hello");
|
||||
remove("/tmp/hello.c");
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)testFilemodPrefixFilter {
|
||||
TSTART("Testing filemod prefix filter");
|
||||
|
||||
NSString *filter =
|
||||
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
|
||||
@"are the same for all non-accelerating observers, and he showed that the speed of light "
|
||||
@"within a vacuum is the same no matter the speed at which an observer travels.";
|
||||
|
||||
// Create a buffer that has 1024 bytes of characters, non-terminating.
|
||||
char buffer[MAXPATHLEN + 1]; // +1 is for the null byte from strlcpy(). It falls off the ledge.
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (![filter getFileSystemRepresentation:buffer + (i * filter.length) maxLength:MAXPATHLEN]) {
|
||||
TFAILINFO("Invalid filemod prefix filter: %s", filter.UTF8String);
|
||||
}
|
||||
}
|
||||
strlcpy(&buffer[1016], "E = mc²", 9); // Fill in the last 8 bytes.
|
||||
|
||||
uint64_t n = 0;
|
||||
uint32_t n_len = 1;
|
||||
|
||||
// The filter should currently be empty. It is reset when the client disconnects.
|
||||
// Fill up the 1024 node capacity.
|
||||
kern_return_t ret =
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
|
||||
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
|
||||
if (ret != kIOReturnSuccess || n != 1024) {
|
||||
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
|
||||
}
|
||||
|
||||
// Make sure it will enforce capacity.
|
||||
const char *too_much = "B";
|
||||
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
|
||||
too_much, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
if (ret != kIOReturnNoResources || n != 1024) {
|
||||
TFAILINFO("Failed enforce capacity: got %llu nodes expected 1024", n);
|
||||
}
|
||||
|
||||
// Make sure it will prune.
|
||||
const char *ignore_it_all = "A";
|
||||
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
|
||||
ignore_it_all, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
// Expect 1 "A" node.
|
||||
if (ret != kIOReturnSuccess || n != 1) {
|
||||
TFAILINFO("Failed to prune the prefix filter: got %llu nodes expected 1", n);
|
||||
}
|
||||
|
||||
// Reset.
|
||||
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
|
||||
NULL, NULL);
|
||||
|
||||
// And fill it back up again.
|
||||
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
|
||||
buffer, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
|
||||
if (ret != kIOReturnSuccess || n != 1024) {
|
||||
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
|
||||
}
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
#pragma mark - Main
|
||||
|
||||
- (void)unloadDaemon {
|
||||
NSTask *t = [[NSTask alloc] init];
|
||||
t.launchPath = @"/bin/launchctl";
|
||||
t.arguments = @[ @"remove", @"com.google.santad" ];
|
||||
t.standardOutput = t.standardError = [NSPipe pipe];
|
||||
[t launch];
|
||||
[t waitUntilExit];
|
||||
}
|
||||
|
||||
- (void)unloadExtension {
|
||||
// Don't check the status of this, the kext may not be loaded..
|
||||
OSStatus ret = KextManagerUnloadKextWithIdentifier(CFSTR("com.google.santa-driver"));
|
||||
if (ret != kOSReturnSuccess && ret != kOSKextReturnNotFound) {
|
||||
NSLog(@"Failed to unload extension: 0x%X", ret);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadExtension {
|
||||
TSTART("Loads extension");
|
||||
|
||||
NSError *error;
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
|
||||
NSString *src = [[fm currentDirectoryPath] stringByAppendingPathComponent:@"santa-driver.kext"];
|
||||
NSString *dest = [NSTemporaryDirectory() stringByAppendingPathComponent:@"santa-driver.kext"];
|
||||
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
|
||||
if (![fm copyItemAtPath:src toPath:dest error:&error] || error) {
|
||||
TFAILINFO("Failed to copy kext: %s", error.description.UTF8String);
|
||||
}
|
||||
|
||||
NSDictionary *attrs = @{
|
||||
NSFileOwnerAccountName : @"root",
|
||||
NSFileGroupOwnerAccountName : @"wheel",
|
||||
NSFilePosixPermissions : @0755
|
||||
};
|
||||
|
||||
[fm setAttributes:attrs ofItemAtPath:dest error:NULL];
|
||||
for (NSString *path in [fm enumeratorAtPath:dest]) {
|
||||
[fm setAttributes:attrs ofItemAtPath:[dest stringByAppendingPathComponent:path] error:NULL];
|
||||
}
|
||||
|
||||
NSURL *destURL = [NSURL fileURLWithPath:dest];
|
||||
OSStatus ret = KextManagerLoadKextWithURL((__bridge CFURLRef)destURL, NULL);
|
||||
if (ret != kOSReturnSuccess) {
|
||||
TFAILINFO("Failed to load kext: 0x%X", ret);
|
||||
}
|
||||
usleep(50000);
|
||||
TPASS();
|
||||
}
|
||||
|
||||
- (void)runTests {
|
||||
printf("-> Connection tests:\n");
|
||||
|
||||
// Test that connection can be established
|
||||
[self connectionTests];
|
||||
|
||||
// Open driver and begin listening for events. Run this on background thread
|
||||
// so we can continue running tests.
|
||||
[self performSelectorInBackground:@selector(beginListening) withObject:nil];
|
||||
|
||||
// Wait for driver to finish getting ready
|
||||
sleep(1);
|
||||
|
||||
printf("\n-> Functional tests:\n");
|
||||
[self receiveAndBlockTests];
|
||||
[self receiveAndCacheTests];
|
||||
[self invalidatesCacheTests];
|
||||
[self invalidatesCacheAutoCloseTest];
|
||||
[self clearCacheTests];
|
||||
[self blocksDeniedTracedBinaries];
|
||||
[self testLargeBinary];
|
||||
[self testPendingTransitiveRules];
|
||||
[self testNoTransitiveRules];
|
||||
[self testFilemodPrefixFilter];
|
||||
|
||||
printf("\n-> Performance tests:\n");
|
||||
[self testCachePerformance];
|
||||
|
||||
printf("\nAll tests passed.\n\n");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
@autoreleasepool {
|
||||
setbuf(stdout, NULL);
|
||||
|
||||
if (getuid() != 0) {
|
||||
printf("Please run as root\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SantaKernelTests *skt = [[SantaKernelTests alloc] init];
|
||||
printf("\nSanta Kernel Tests\n==================\n\n");
|
||||
printf("-> Loading tests:\n");
|
||||
[skt unloadDaemon];
|
||||
[skt unloadExtension];
|
||||
[skt loadExtension];
|
||||
printf("\n");
|
||||
|
||||
[skt runTests];
|
||||
[skt unloadExtension];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#include <mach/mach_types.h>
|
||||
|
||||
#define STR_EXPAND(tok) #tok
|
||||
#define STR(tok) STR_EXPAND(tok)
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern kern_return_t _start(kmod_info_t *ki, void *data);
|
||||
extern kern_return_t _stop(kmod_info_t *ki, void *data);
|
||||
|
||||
__attribute__((visibility("default"))) \
|
||||
KMOD_EXPLICIT_DECL(com.google.santa-driver, STR(SANTA_VERSION), _start, _stop)
|
||||
|
||||
__private_extern__ kmod_start_func_t *_realmain = 0;
|
||||
__private_extern__ kmod_stop_func_t *_antimain = 0;
|
||||
|
||||
__private_extern__ int _kext_apple_cc = __APPLE_CC__ ;
|
||||
}
|
||||
|
||||
#include <IOKit/IOService.h>
|
||||
#include <IOKit/IOUserClient.h>
|
||||
|
||||
// The macOS 10.15 SDK added these Dispatch methods but they aren't
|
||||
// available on older macOS versions and so prevent kext linking.
|
||||
// Defining them here as hidden weak no-op's fixes linking and seems to work.
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSMetaClassBase::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSObject::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOService::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOUserClient::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
|
||||
@@ -3,7 +3,10 @@ load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santactl_lib",
|
||||
@@ -33,11 +36,11 @@ objc_library(
|
||||
sdk_frameworks = ["IOKit"],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTRule",
|
||||
|
||||
@@ -80,7 +80,7 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
|
||||
SNTConfigurator *configurator = [SNTConfigurator configurator];
|
||||
|
||||
BOOL cachingEnabled = (![configurator enableSystemExtension] || [configurator enableSysxCache]);
|
||||
BOOL cachingEnabled = [configurator enableSysxCache];
|
||||
|
||||
// Kext status
|
||||
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
|
||||
@@ -188,6 +188,10 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
@"watchdog_ram_events" : @(ramEvents),
|
||||
@"watchdog_cpu_peak" : @(cpuPeak),
|
||||
@"watchdog_ram_peak" : @(ramPeak),
|
||||
@"block_usb" : @(configurator.blockUSBMount),
|
||||
@"remount_usb_mode" : (configurator.blockUSBMount && configurator.remountUSBMode.count
|
||||
? configurator.remountUSBMode
|
||||
: @""),
|
||||
},
|
||||
@"database" : @{
|
||||
@"binary_rules" : @(binaryRuleCount),
|
||||
@@ -224,11 +228,17 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
|
||||
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
|
||||
printf(" %-25s | %s\n", "USB Remounting Mode:",
|
||||
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
|
||||
}
|
||||
|
||||
if (cachingEnabled) {
|
||||
printf(">>> Cache Info\n");
|
||||
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
|
||||
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
|
||||
}
|
||||
|
||||
printf(">>> Database Info\n");
|
||||
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
|
||||
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
|
||||
|
||||
@@ -13,13 +13,12 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <IOKit/kext/KextManager.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@@ -50,7 +49,6 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
if ([arguments containsObject:@"--json"]) {
|
||||
NSDictionary *versions = @{
|
||||
@"santa-driver" : [self santaKextVersion],
|
||||
@"santad" : [self santadVersion],
|
||||
@"santactl" : [self santactlVersion],
|
||||
@"SantaGUI" : [self santaAppVersion],
|
||||
@@ -62,7 +60,6 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
encoding:NSUTF8StringEncoding];
|
||||
printf("%s\n", [versionsStr UTF8String]);
|
||||
} else {
|
||||
printf("%-15s | %s\n", "santa-driver", [[self santaKextVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santad", [[self santadVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santactl", [[self santactlVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "SantaGUI", [[self santaAppVersion] UTF8String]);
|
||||
@@ -70,38 +67,25 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
exit(0);
|
||||
}
|
||||
|
||||
- (NSString *)santaKextVersion {
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
return @"un-needed (SystemExtension being used)";
|
||||
}
|
||||
|
||||
NSDictionary *loadedKexts = CFBridgingRelease(KextManagerCopyLoadedKextInfo(
|
||||
(__bridge CFArrayRef) @[ @(USERCLIENT_ID) ], (__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
|
||||
|
||||
if (loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"]) {
|
||||
return loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
SNTFileInfo *driverInfo = [[SNTFileInfo alloc] initWithPath:@(kKextPath)];
|
||||
if (driverInfo) {
|
||||
return [driverInfo.bundleVersion stringByAppendingString:@" (unloaded)"];
|
||||
}
|
||||
|
||||
return @"not found";
|
||||
- (NSString *)composeVersionsFromDict:(NSDictionary *)dict {
|
||||
if (!dict[@"CFBundleVersion"]) return @"";
|
||||
NSString *productVersion = dict[@"CFBundleShortVersionString"];
|
||||
NSString *buildVersion = [[dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
|
||||
return [NSString stringWithFormat:@"%@ (build %@)", productVersion, buildVersion];
|
||||
}
|
||||
|
||||
- (NSString *)santadVersion {
|
||||
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaDPath)];
|
||||
return daemonInfo.bundleVersion ?: @"";
|
||||
return [self composeVersionsFromDict:daemonInfo.infoPlist];
|
||||
}
|
||||
|
||||
- (NSString *)santaAppVersion {
|
||||
SNTFileInfo *guiInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaAppPath)];
|
||||
return guiInfo.bundleVersion ?: @"";
|
||||
return [self composeVersionsFromDict:guiInfo.infoPlist];
|
||||
}
|
||||
|
||||
- (NSString *)santactlVersion {
|
||||
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] ?: @"";
|
||||
return [self composeVersionsFromDict:[[NSBundle mainBundle] infoDictionary]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_bundle")
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
objc_library(
|
||||
name = "santad_lib",
|
||||
name = "database_controller",
|
||||
srcs = [
|
||||
"DataLayer/SNTDatabaseTable.h",
|
||||
"DataLayer/SNTDatabaseTable.m",
|
||||
@@ -12,21 +17,132 @@ objc_library(
|
||||
"DataLayer/SNTEventTable.m",
|
||||
"DataLayer/SNTRuleTable.h",
|
||||
"DataLayer/SNTRuleTable.m",
|
||||
"SNTDatabaseController.h",
|
||||
"SNTDatabaseController.m",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTEventProvider",
|
||||
hdrs = ["EventProviders/SNTEventProvider.h"],
|
||||
deps = [
|
||||
"//Source/common:SNTCommon",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "endpoint_security_manager",
|
||||
srcs = [
|
||||
"EventProviders/SNTEndpointSecurityManager.h",
|
||||
"EventProviders/SNTEndpointSecurityManager.mm",
|
||||
],
|
||||
sdk_dylibs = [
|
||||
"EndpointSecurity",
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":SNTEventProvider",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "event_logs_common",
|
||||
srcs = [
|
||||
"DataLayer/SNTDatabaseTable.h",
|
||||
"DataLayer/SNTRuleTable.h",
|
||||
"Logs/SNTEventLog.h",
|
||||
"Logs/SNTEventLog.m",
|
||||
"Logs/SNTFileEventLog.h",
|
||||
"Logs/SNTProtobufEventLog.h",
|
||||
"Logs/SNTSyslogEventLog.h",
|
||||
"SNTDatabaseController.h",
|
||||
],
|
||||
deps = [
|
||||
":database_controller",
|
||||
"//Source/common:SNTAllowlistInfo",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "protobuf_event_logs",
|
||||
srcs = [
|
||||
"Logs/SNTLogOutput.h",
|
||||
"Logs/SNTProtobufEventLog.h",
|
||||
"Logs/SNTProtobufEventLog.m",
|
||||
"Logs/SNTSimpleMaildir.h",
|
||||
"Logs/SNTSimpleMaildir.m",
|
||||
],
|
||||
deps = [
|
||||
":event_logs_common",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:log_objc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "syslog_event_logs",
|
||||
srcs = [
|
||||
"EventProviders/SNTEndpointSecurityManager.h",
|
||||
"EventProviders/SNTEventProvider.h",
|
||||
"Logs/SNTSyslogEventLog.h",
|
||||
"Logs/SNTSyslogEventLog.m",
|
||||
],
|
||||
deps = [
|
||||
":endpoint_security_manager",
|
||||
":event_logs_common",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "file_event_logs",
|
||||
srcs = [
|
||||
"Logs/SNTFileEventLog.h",
|
||||
"Logs/SNTFileEventLog.m",
|
||||
],
|
||||
deps = [
|
||||
":event_logs_common",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "event_logs",
|
||||
deps = [
|
||||
":file_event_logs",
|
||||
":protobuf_event_logs",
|
||||
":syslog_event_logs",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "santad_lib",
|
||||
srcs = [
|
||||
"DataLayer/SNTDatabaseTable.h",
|
||||
"DataLayer/SNTEventTable.h",
|
||||
"DataLayer/SNTRuleTable.h",
|
||||
"EventProviders/SNTCachingEndpointSecurityManager.h",
|
||||
"EventProviders/SNTCachingEndpointSecurityManager.mm",
|
||||
"EventProviders/SNTDeviceManager.h",
|
||||
"EventProviders/SNTDeviceManager.mm",
|
||||
"EventProviders/SNTDriverManager.h",
|
||||
"EventProviders/SNTDriverManager.m",
|
||||
"EventProviders/SNTEndpointSecurityManager.h",
|
||||
"EventProviders/SNTEndpointSecurityManager.mm",
|
||||
"EventProviders/SNTEventProvider.h",
|
||||
"Logs/SNTEventLog.h",
|
||||
"Logs/SNTEventLog.m",
|
||||
"Logs/SNTFileEventLog.h",
|
||||
"Logs/SNTFileEventLog.m",
|
||||
"Logs/SNTSyslogEventLog.h",
|
||||
"Logs/SNTSyslogEventLog.m",
|
||||
"SNTApplication.h",
|
||||
"SNTApplication.m",
|
||||
"SNTCompilerController.h",
|
||||
@@ -34,7 +150,6 @@ objc_library(
|
||||
"SNTDaemonControlController.h",
|
||||
"SNTDaemonControlController.m",
|
||||
"SNTDatabaseController.h",
|
||||
"SNTDatabaseController.m",
|
||||
"SNTExecutionController.h",
|
||||
"SNTExecutionController.m",
|
||||
"SNTNotificationQueue.h",
|
||||
@@ -54,26 +169,24 @@ objc_library(
|
||||
"IOKit",
|
||||
],
|
||||
deps = [
|
||||
":database_controller",
|
||||
":endpoint_security_manager",
|
||||
":event_logs",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCMetricServiceInterface",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"//Source/common:SNTXPCSyncdInterface",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/santad:SNTApplicationCoreMetrics",
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
@@ -158,10 +271,10 @@ santa_unit_test(
|
||||
":santad_lib",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
@@ -230,7 +343,7 @@ santa_unit_test(
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
@@ -250,7 +363,7 @@ santa_unit_test(
|
||||
":DiskArbitrationTestLib",
|
||||
":EndpointSecurityTestLib",
|
||||
":santad_lib",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
"@OCMock",
|
||||
@@ -317,14 +430,31 @@ santa_unit_test(
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTProtobufEventLogTest",
|
||||
srcs = [
|
||||
"Logs/SNTProtobufEventLogTest.m",
|
||||
],
|
||||
minimum_os_version = "10.15",
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
":event_logs",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:log_objc_proto",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":SNTApplicationCoreMetricsTest",
|
||||
":SNTApplicationTest",
|
||||
":SNTDeviceManagerTest",
|
||||
":SNTEndpointSecurityManagerTest",
|
||||
":SNTEventTableTest",
|
||||
":SNTExecutionControllerTest",
|
||||
":SNTProtobufEventLogTest",
|
||||
":SNTRuleTableTest",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
|
||||
CF_EXTERN_C_BEGIN
|
||||
es_string_token_t MakeStringToken(const NSString *_Nonnull s);
|
||||
|
||||
es_file_t MakeESFile(const char *_Nonnull path);
|
||||
es_process_t MakeESProcess(es_file_t *_Nonnull esFile);
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *_Nonnull instigator, struct timespec ts);
|
||||
CF_EXTERN_C_END
|
||||
|
||||
@class ESMessage;
|
||||
|
||||
@@ -26,6 +26,35 @@ es_string_token_t MakeStringToken(const NSString *_Nonnull s) {
|
||||
.data = [s UTF8String],
|
||||
};
|
||||
}
|
||||
|
||||
es_file_t MakeESFile(const char *path) {
|
||||
es_file_t esFile = {};
|
||||
|
||||
esFile.path.data = path;
|
||||
esFile.path.length = strlen(path);
|
||||
esFile.path_truncated = false;
|
||||
|
||||
// Note: stat info is currently unused / not populated
|
||||
|
||||
return esFile;
|
||||
}
|
||||
|
||||
es_process_t MakeESProcess(es_file_t *esFile) {
|
||||
es_process_t esProc = {};
|
||||
esProc.executable = esFile;
|
||||
return esProc;
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t eventType, es_process_t *instigator, struct timespec ts) {
|
||||
es_message_t esMsg = {};
|
||||
|
||||
esMsg.time = ts;
|
||||
esMsg.event_type = eventType;
|
||||
esMsg.process = instigator;
|
||||
|
||||
return esMsg;
|
||||
}
|
||||
|
||||
CF_EXTERN_C_END
|
||||
|
||||
@implementation ESMessage
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
#include "Source/common/SNTDeviceEvent.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^SNTDeviceBlockCallback)(SNTDeviceEvent *event);
|
||||
|
||||
/*
|
||||
* Manages DiskArbitration and EndpointSecurity to monitor/block/remount USB
|
||||
* storage devices.
|
||||
@@ -25,9 +31,12 @@
|
||||
@property(nonatomic, readwrite) BOOL subscribed;
|
||||
@property(nonatomic, readwrite) BOOL blockUSBMount;
|
||||
@property(nonatomic, readwrite, nullable) NSArray<NSString *> *remountArgs;
|
||||
@property(nonatomic, nullable) SNTDeviceBlockCallback deviceBlockCallback;
|
||||
|
||||
- (instancetype _Nonnull)init;
|
||||
- (instancetype)init;
|
||||
- (void)listen;
|
||||
- (BOOL)subscribed;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/santad/Logs/SNTEventLog.h"
|
||||
|
||||
@@ -105,6 +106,8 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SNTDeviceManager ()
|
||||
|
||||
@property DASessionRef diskArbSession;
|
||||
@@ -115,7 +118,7 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
|
||||
@implementation SNTDeviceManager
|
||||
|
||||
- (instancetype _Nonnull)init API_AVAILABLE(macos(10.15)) {
|
||||
- (instancetype)init API_AVAILABLE(macos(10.15)) {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_blockUSBMount = false;
|
||||
@@ -216,9 +219,14 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
return;
|
||||
}
|
||||
|
||||
SNTDeviceEvent *event = [[SNTDeviceEvent alloc]
|
||||
initWithOnName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntonname]
|
||||
fromName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntfromname]];
|
||||
|
||||
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
|
||||
|
||||
if (shouldRemount) {
|
||||
event.remountArgs = self.remountArgs;
|
||||
long remountOpts = mountArgsToMask(self.remountArgs);
|
||||
if (mountMode & remountOpts) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
@@ -231,6 +239,11 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
newMode);
|
||||
[self remount:disk mountMode:newMode];
|
||||
}
|
||||
|
||||
if (self.deviceBlockCallback) {
|
||||
self.deviceBlockCallback(event);
|
||||
}
|
||||
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
}
|
||||
|
||||
@@ -298,3 +311,5 @@ long mountArgsToMask(NSArray<NSString *> *args) {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <sys/mount.h>
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/santad/EventProviders/SNTDeviceManager.h"
|
||||
|
||||
#import "Source/santad/EventProviders/DiskArbitrationTestUtil.h"
|
||||
@@ -132,10 +133,28 @@
|
||||
deviceManager.blockUSBMount = YES;
|
||||
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
|
||||
|
||||
__block NSString *gotmntonname, *gotmntfromname;
|
||||
__block NSArray<NSString *> *gotRemountedArgs;
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
gotRemountedArgs = event.remountArgs;
|
||||
gotmntonname = event.mntonname;
|
||||
gotmntfromname = event.mntfromname;
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, YES);
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:60.0];
|
||||
|
||||
XCTAssertEqualObjects(gotRemountedArgs, deviceManager.remountArgs);
|
||||
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
- (void)testBlockNoRemount {
|
||||
@@ -148,10 +167,27 @@
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
|
||||
|
||||
__block NSString *gotmntonname, *gotmntfromname;
|
||||
__block NSArray<NSString *> *gotRemountedArgs;
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
gotRemountedArgs = event.remountArgs;
|
||||
gotmntonname = event.mntonname;
|
||||
gotmntfromname = event.mntfromname;
|
||||
[expectation fulfill];
|
||||
};
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, NO);
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:60.0];
|
||||
|
||||
XCTAssertNil(gotRemountedArgs);
|
||||
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
|
||||
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
@class SNTNotificationMessage;
|
||||
|
||||
///
|
||||
/// Manages the connection between daemon and kernel.
|
||||
///
|
||||
@interface SNTDriverManager : NSObject <SNTEventProvider>
|
||||
|
||||
///
|
||||
/// Handles locating and connecting to the driver. If driver is not loaded, will
|
||||
/// sleep until it is. If driver is loaded but connection fails (like if a client is
|
||||
/// already connected), will return nil.
|
||||
///
|
||||
- (instancetype)init;
|
||||
|
||||
///
|
||||
/// Unloads the driver.
|
||||
///
|
||||
+ (void)unloadDriver;
|
||||
|
||||
///
|
||||
/// Handles requests from the kernel using the given block.
|
||||
/// @note Loops indefinitely unless there is an error trying to read data from the data queue.
|
||||
///
|
||||
- (void)listenForDecisionRequests:(void (^)(santa_message_t message))callback;
|
||||
|
||||
///
|
||||
/// Handles requests from the kernel using the given block.
|
||||
/// @note Loops indefinitely unless there is an error trying to read data from the data queue.
|
||||
///
|
||||
- (void)listenForLogRequests:(void (^)(santa_message_t message))callback;
|
||||
|
||||
///
|
||||
/// Sends a response to a query back to the kernel.
|
||||
///
|
||||
- (kern_return_t)postAction:(santa_action_t)action forMessage:(santa_message_t)sm;
|
||||
|
||||
///
|
||||
/// Get the number of binaries in the kernel's caches.
|
||||
///
|
||||
- (NSArray<NSNumber *> *)cacheCounts;
|
||||
|
||||
///
|
||||
/// Return an array representing all buckets in the kernel's decision cache where each number
|
||||
/// is the number of items in that bucket.
|
||||
///
|
||||
- (NSArray<NSNumber *> *)cacheBucketCount;
|
||||
|
||||
///
|
||||
/// Flush the kernel's binary cache.
|
||||
///
|
||||
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly;
|
||||
|
||||
///
|
||||
/// Check the kernel cache for a VnodeID.
|
||||
///
|
||||
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID;
|
||||
|
||||
///
|
||||
/// Returns whether the connection to the driver has been established.
|
||||
///
|
||||
@property(readonly) BOOL connectionEstablished;
|
||||
|
||||
///
|
||||
/// Remove single entry from the kernel cache for given VnodeID.
|
||||
///
|
||||
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId;
|
||||
|
||||
///
|
||||
/// Adds in-kernel prefix filters for file modification logs.
|
||||
///
|
||||
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters;
|
||||
|
||||
///
|
||||
/// Resets the filter.
|
||||
///
|
||||
- (void)fileModificationPrefixFilterReset;
|
||||
|
||||
@end
|
||||
@@ -1,314 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/EventProviders/SNTDriverManager.h"
|
||||
|
||||
#import <IOKit/IODataQueueClient.h>
|
||||
#import <IOKit/kext/KextManager.h>
|
||||
|
||||
#include <mach/mach_port.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
@interface SNTDriverManager ()
|
||||
@property io_connect_t connection;
|
||||
@property(readwrite) BOOL connectionEstablished;
|
||||
@property(nonatomic, readonly) dispatch_queue_t dmAuthQueue;
|
||||
@property(nonatomic, readonly) dispatch_queue_t dmNotifiyQueue;
|
||||
@end
|
||||
|
||||
@implementation SNTDriverManager
|
||||
|
||||
#pragma mark init/dealloc
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_dmAuthQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.dm_auth", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(_dmAuthQueue,
|
||||
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0));
|
||||
_dmNotifiyQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.dm_notify", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(_dmNotifiyQueue, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
|
||||
|
||||
CFDictionaryRef classToMatch = IOServiceMatching(USERCLIENT_CLASS);
|
||||
if (!classToMatch) {
|
||||
LOGE(@"Failed to create matching dictionary");
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Attempt to load driver. It may already be running, so ignore any return value.
|
||||
KextManagerLoadKextWithIdentifier(CFSTR(USERCLIENT_ID), NULL);
|
||||
|
||||
// Wait for the driver to appear
|
||||
[self waitForDriver:classToMatch];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
IOServiceClose(_connection);
|
||||
}
|
||||
|
||||
+ (void)unloadDriver {
|
||||
KextManagerUnloadKextWithIdentifier(CFSTR(USERCLIENT_ID));
|
||||
}
|
||||
|
||||
#pragma mark Driver Waiting
|
||||
|
||||
// Helper function used with IOServiceAddMatchingNotification which expects
|
||||
// a DriverAppearedBlock to be passed as the 'reference' argument. The block
|
||||
// will be called for each object iterated over and if the block wants to keep
|
||||
// the object, it should call IOObjectRetain().
|
||||
typedef void (^DriverAppearedBlock)(io_object_t object);
|
||||
static void driverAppearedHandler(void *info, io_iterator_t iterator) {
|
||||
DriverAppearedBlock block = (__bridge DriverAppearedBlock)info;
|
||||
if (!block) return;
|
||||
io_object_t object = 0;
|
||||
while ((object = IOIteratorNext(iterator))) {
|
||||
block(object);
|
||||
IOObjectRelease(object);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the driver to appear, then attach to and open it.
|
||||
- (void)waitForDriver:(CFDictionaryRef CF_RELEASES_ARGUMENT)matchingDict {
|
||||
IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
|
||||
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop],
|
||||
IONotificationPortGetRunLoopSource(notificationPort), kCFRunLoopDefaultMode);
|
||||
|
||||
io_iterator_t iterator = 0;
|
||||
|
||||
DriverAppearedBlock block = ^(io_object_t object) {
|
||||
// This calls `initWithTask`, `attach` and `start` in `SantaDriverClient`
|
||||
kern_return_t kr = IOServiceOpen(object, mach_task_self(), 0, &self->_connection);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
LOGE(@"Failed to open santa-driver service: 0x%X", kr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Call `open` in `SantaDriverClient`
|
||||
kr = IOConnectCallMethod(self->_connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
if (kr == kIOReturnExclusiveAccess) {
|
||||
LOGE(@"A client is already connected");
|
||||
exit(2);
|
||||
} else if (kr != kIOReturnSuccess) {
|
||||
LOGE(@"An error occurred while opening the connection: 0x%X", kr);
|
||||
exit(3);
|
||||
}
|
||||
|
||||
// Release the iterator to disarm the notifications.
|
||||
IOObjectRelease(iterator);
|
||||
IONotificationPortDestroy(notificationPort);
|
||||
|
||||
self->_connectionEstablished = YES;
|
||||
};
|
||||
|
||||
LOGI(@"Waiting for Santa driver to become available");
|
||||
|
||||
IOServiceAddMatchingNotification(notificationPort, kIOMatchedNotification, matchingDict,
|
||||
driverAppearedHandler, (__bridge_retained void *)block,
|
||||
&iterator);
|
||||
|
||||
// Call the handler once to 'empty' the iterator, arming it. If the driver is already loaded
|
||||
// this will immediately cause the connection to be fully established.
|
||||
driverAppearedHandler((__bridge_retained void *)block, iterator);
|
||||
}
|
||||
|
||||
#pragma mark Incoming messages
|
||||
|
||||
- (void)listenForDecisionRequests:(void (^)(santa_message_t))callback {
|
||||
[self listenForRequestsOfType:QUEUETYPE_DECISION withCallback:callback];
|
||||
}
|
||||
|
||||
- (void)listenForLogRequests:(void (^)(santa_message_t))callback {
|
||||
[self listenForRequestsOfType:QUEUETYPE_LOG withCallback:callback];
|
||||
}
|
||||
|
||||
- (void)listenForRequestsOfType:(santa_queuetype_t)type
|
||||
withCallback:(void (^)(santa_message_t))callback {
|
||||
while (!self.connectionEstablished)
|
||||
usleep(100000); // 100ms
|
||||
|
||||
// Allocate a mach port to receive notifactions from the IODataQueue
|
||||
mach_port_t receivePort = IODataQueueAllocateNotificationPort();
|
||||
if (receivePort == MACH_PORT_NULL) {
|
||||
LOGD(@"Failed to allocate notification port");
|
||||
return;
|
||||
}
|
||||
|
||||
// This will call registerNotificationPort() inside our user client class
|
||||
kern_return_t kr = IOConnectSetNotificationPort(self.connection, type, receivePort, 0);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
LOGD(@"Failed to register notification port for type %d: 0x%X", type, kr);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will call clientMemoryForType() inside our user client class.
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
kr = IOConnectMapMemory(self.connection, type, mach_task_self(), &address, &size, kIOMapAnywhere);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
LOGD(@"Failed to map memory for type %d: 0x%X", type, kr);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
return;
|
||||
}
|
||||
|
||||
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
|
||||
|
||||
do {
|
||||
while (IODataQueueDataAvailable(queueMemory)) {
|
||||
santa_message_t vdata;
|
||||
uint32_t dataSize = sizeof(vdata);
|
||||
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
|
||||
if (kr == kIOReturnSuccess) {
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION: {
|
||||
dispatch_async(self.dmAuthQueue, ^{
|
||||
callback(vdata);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case QUEUETYPE_LOG: {
|
||||
dispatch_async(self.dmNotifiyQueue, ^{
|
||||
callback(vdata);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Error dequeuing data for type %d: 0x%X", type, kr);
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
|
||||
|
||||
IOConnectUnmapMemory(self.connection, type, mach_task_self(), address);
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
}
|
||||
|
||||
#pragma mark Outgoing messages
|
||||
|
||||
- (kern_return_t)postAction:(santa_action_t)action forMessage:(santa_message_t)sm {
|
||||
santa_vnode_id_t vnodeId = sm.vnode_id;
|
||||
switch (action) {
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientAllowBinary, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
case ACTION_RESPOND_DENY:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientDenyBinary, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
case ACTION_RESPOND_ACK:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientAcknowledgeBinary, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientAllowCompiler, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
default: return KERN_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheCounts {
|
||||
uint32_t input_count = 2;
|
||||
uint64_t cache_counts[2] = {0, 0};
|
||||
|
||||
IOConnectCallScalarMethod(_connection, kSantaUserClientCacheCount, 0, 0, cache_counts,
|
||||
&input_count);
|
||||
|
||||
return @[ @(cache_counts[0]), @(cache_counts[1]) ];
|
||||
}
|
||||
|
||||
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly {
|
||||
const uint64_t nonRoot = nonRootOnly;
|
||||
|
||||
return IOConnectCallScalarMethod(_connection, kSantaUserClientClearCache, &nonRoot, 1, 0, 0) ==
|
||||
KERN_SUCCESS;
|
||||
}
|
||||
|
||||
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId {
|
||||
return IOConnectCallStructMethod(_connection, kSantaUserClientRemoveCacheEntry, &vnodeId,
|
||||
sizeof(vnodeId), 0, 0);
|
||||
}
|
||||
|
||||
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
|
||||
uint64_t output;
|
||||
uint32_t outputCnt = 1;
|
||||
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientCheckCache, NULL, 0, &vnodeID,
|
||||
sizeof(santa_vnode_id_t), &output, &outputCnt, NULL, 0);
|
||||
return (santa_action_t)output;
|
||||
}
|
||||
|
||||
- (NSArray *)cacheBucketCount {
|
||||
santa_bucket_count_t counts = {};
|
||||
size_t size = sizeof(counts);
|
||||
|
||||
NSMutableArray *a = [NSMutableArray array];
|
||||
|
||||
do {
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientCacheBucketCount, &counts, size,
|
||||
&counts, &size);
|
||||
for (uint64_t i = 0; i < sizeof(counts.per_bucket) / sizeof(uint16_t); ++i) {
|
||||
[a addObject:@(counts.per_bucket[i])];
|
||||
}
|
||||
} while (counts.start > 0);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters {
|
||||
while (!self.connectionEstablished)
|
||||
usleep(100000); // 100ms
|
||||
|
||||
uint64_t n = 0;
|
||||
uint32_t n_len = 1;
|
||||
|
||||
for (NSString *filter in filters) {
|
||||
char buffer[MAXPATHLEN];
|
||||
if (![filter getFileSystemRepresentation:buffer maxLength:MAXPATHLEN]) {
|
||||
LOGE(@"Invalid filemod prefix filter: %@", filter);
|
||||
continue;
|
||||
}
|
||||
|
||||
kern_return_t ret =
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
|
||||
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
|
||||
if (ret != kIOReturnSuccess) {
|
||||
LOGE(@"Failed to add prefix filter: %s error: 0x%x", buffer, ret);
|
||||
|
||||
// If the tree is full, stop.
|
||||
// TODO(bur): Maybe continue here with some smarts around the size? Shorter, not yet iterated
|
||||
// prefixes (that may fit) are ignored. Seems worth it not to spam the driver.
|
||||
if (ret == kIOReturnNoResources) {
|
||||
LOGE(@"Prefix filter tree is full!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
LOGI(@"Added prefix filter: %s", buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fileModificationPrefixFilterReset {
|
||||
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,14 +14,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
#include "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
|
||||
const pid_t PID_MAX = 99999;
|
||||
|
||||
@interface SNTEndpointSecurityManager : NSObject <SNTEventProvider>
|
||||
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file;
|
||||
|
||||
@@ -29,6 +26,10 @@ const pid_t PID_MAX = 99999;
|
||||
- (void)setIsCompilerPID:(pid_t)pid;
|
||||
- (void)setNotCompilerPID:(pid_t)pid;
|
||||
|
||||
// Returns YES if the path was truncated.
|
||||
// The populated buffer will be NUL terminated.
|
||||
+ (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size;
|
||||
|
||||
@property(nonatomic, copy) void (^decisionCallback)(santa_message_t);
|
||||
@property(nonatomic, copy) void (^logCallback)(santa_message_t);
|
||||
@property(readonly, nonatomic) es_client_t *client;
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <libproc.h>
|
||||
#include <atomic>
|
||||
|
||||
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
|
||||
static const pid_t PID_MAX = 99999;
|
||||
|
||||
@interface SNTEndpointSecurityManager () {
|
||||
std::atomic<bool> _compilerPIDs[PID_MAX];
|
||||
}
|
||||
@@ -111,9 +114,9 @@
|
||||
// Create a transitive rule if the file was modified by a running compiler
|
||||
if ([self isCompilerPID:pid]) {
|
||||
santa_message_t sm = {};
|
||||
BOOL truncated = [self populateBufferFromESFile:m->event.close.target
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)];
|
||||
BOOL truncated = [SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)];
|
||||
if (truncated) {
|
||||
LOGE(@"CLOSE: error creating transitive rule, the path is truncated: path=%s pid=%d",
|
||||
sm.path, pid);
|
||||
@@ -176,6 +179,14 @@
|
||||
});
|
||||
return;
|
||||
}
|
||||
case ES_EVENT_TYPE_NOTIFY_UNMOUNT: {
|
||||
// Flush the non-root cache - the root disk cannot be unmounted
|
||||
// so it isn't necessary to flush its cache.
|
||||
[self flushCacheNonRootOnly:YES];
|
||||
|
||||
// Skip all other processing
|
||||
return;
|
||||
}
|
||||
case ES_EVENT_TYPE_NOTIFY_FORK: {
|
||||
// Skip the standard pipeline and just log.
|
||||
if (![config enableForkAndExitLogging]) return;
|
||||
@@ -373,10 +384,9 @@
|
||||
targetProcess = m->process;
|
||||
NSString *p = @(m->event.link.target_dir->path.data);
|
||||
p = [p stringByAppendingPathComponent:@(m->event.link.target_filename.data)];
|
||||
[self populateBufferFromString:p.UTF8String
|
||||
length:p.length
|
||||
buffer:sm.newpath
|
||||
size:sizeof(sm.newpath)];
|
||||
[SNTEndpointSecurityManager populateBufferFromString:p.UTF8String
|
||||
buffer:sm.newpath
|
||||
size:sizeof(sm.newpath)];
|
||||
callback = self.logCallback;
|
||||
break;
|
||||
}
|
||||
@@ -395,7 +405,9 @@
|
||||
|
||||
// Deny auth exec events if the path doesn't fit in the santa message.
|
||||
// TODO(bur/rah): Add support for larger paths.
|
||||
if ([self populateBufferFromESFile:targetFile buffer:sm.path size:sizeof(sm.path)] &&
|
||||
if ([SNTEndpointSecurityManager populateBufferFromESFile:targetFile
|
||||
buffer:sm.path
|
||||
size:sizeof(sm.path)] &&
|
||||
m->event_type == ES_EVENT_TYPE_AUTH_EXEC) {
|
||||
LOGE(@"path is truncated, deny: %s", sm.path);
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
@@ -417,6 +429,9 @@
|
||||
sm.ppid = targetProcess->original_ppid;
|
||||
proc_name((m->event_type == ES_EVENT_TYPE_AUTH_EXEC) ? sm.ppid : sm.pid, sm.pname, 1024);
|
||||
callback(sm);
|
||||
if (sm.args_array) {
|
||||
CFBridgingRelease(sm.args_array);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listenForDecisionRequests:(void (^)(santa_message_t))callback API_AVAILABLE(macos(10.15)) {
|
||||
@@ -433,6 +448,10 @@
|
||||
// This is in the decision callback because it's used for detecting
|
||||
// the exit of a 'compiler' used by transitive whitelisting.
|
||||
ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
|
||||
// This is in the decision callback because it's used for clearing the
|
||||
// caches when a disk is unmounted.
|
||||
ES_EVENT_TYPE_NOTIFY_UNMOUNT,
|
||||
};
|
||||
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
|
||||
if (sret != ES_RETURN_SUCCESS) LOGE(@"Unable to subscribe to auth events: %d", sret);
|
||||
@@ -440,7 +459,7 @@
|
||||
// There's a gap between creating a client and subscribing to events. Creating the client
|
||||
// triggers a cache flush automatically but any events that happen in this gap could be allowed
|
||||
// and cached, so we force the cache to flush again.
|
||||
[self flushCacheNonRootOnly:YES];
|
||||
[self flushCacheNonRootOnly:NO];
|
||||
}
|
||||
|
||||
- (void)listenForLogRequests:(void (^)(santa_message_t))callback API_AVAILABLE(macos(10.15)) {
|
||||
@@ -523,22 +542,17 @@
|
||||
#pragma mark helpers
|
||||
|
||||
// Returns YES if the path was truncated.
|
||||
// The populated path will be NUL terminated.
|
||||
- (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
|
||||
return [self populateBufferFromString:file->path.data
|
||||
length:file->path.length
|
||||
buffer:buffer
|
||||
size:size];
|
||||
// The populated buffer will be NUL terminated.
|
||||
+ (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
|
||||
return [SNTEndpointSecurityManager populateBufferFromString:file->path.data
|
||||
buffer:buffer
|
||||
size:size];
|
||||
}
|
||||
|
||||
// Returns YES if the path was truncated.
|
||||
// The populated path will be NUL terminated.
|
||||
- (BOOL)populateBufferFromString:(const char *)string
|
||||
length:(size_t)length
|
||||
buffer:(char *)buffer
|
||||
size:(size_t)size {
|
||||
if (length++ > size) length = size;
|
||||
return strlcpy(buffer, string, length) >= length;
|
||||
// The populated buffer will be NUL terminated.
|
||||
+ (BOOL)populateBufferFromString:(const char *)string buffer:(char *)buffer size:(size_t)size {
|
||||
return strlcpy(buffer, string, size) >= size;
|
||||
}
|
||||
|
||||
- (BOOL)populateRenamedNewPathFromESMessage:(es_event_rename_t)mv
|
||||
@@ -549,16 +563,15 @@
|
||||
case ES_DESTINATION_TYPE_NEW_PATH: {
|
||||
NSString *p = @(mv.destination.new_path.dir->path.data);
|
||||
p = [p stringByAppendingPathComponent:@(mv.destination.new_path.filename.data)];
|
||||
truncated = [self populateBufferFromString:p.UTF8String
|
||||
length:p.length
|
||||
buffer:buffer
|
||||
size:size];
|
||||
truncated = [SNTEndpointSecurityManager populateBufferFromString:p.UTF8String
|
||||
buffer:buffer
|
||||
size:size];
|
||||
break;
|
||||
}
|
||||
case ES_DESTINATION_TYPE_EXISTING_FILE: {
|
||||
truncated = [self populateBufferFromESFile:mv.destination.existing_file
|
||||
buffer:buffer
|
||||
size:size];
|
||||
truncated = [SNTEndpointSecurityManager populateBufferFromESFile:mv.destination.existing_file
|
||||
buffer:buffer
|
||||
size:size];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
@protocol SNTEventProvider <NSObject>
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string>Google, LLC.</string>
|
||||
<key>NSSystemExtensionUsageDescription</key>
|
||||
<string>Santa knows who is naughty and nice.</string>
|
||||
<key>NSEndpointSecurityEarlyBoot</key>
|
||||
<true/>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>com.google.santa.daemon</string>
|
||||
</dict>
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
|
||||
@class SNTCachedDecision;
|
||||
@class SNTStoredEvent;
|
||||
@class SNTAllowlistInfo;
|
||||
|
||||
///
|
||||
/// Abstract interface for logging execution and file write events to syslog
|
||||
@@ -37,7 +38,8 @@
|
||||
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events;
|
||||
- (void)logFork:(santa_message_t)message;
|
||||
- (void)logExit:(santa_message_t)message;
|
||||
- (void)writeLog:(NSString *)log;
|
||||
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo;
|
||||
- (void)forceFlush;
|
||||
|
||||
// Methods for storing, retrieving, and removing cached decisions.
|
||||
- (void)cacheDecision:(SNTCachedDecision *)cd;
|
||||
@@ -54,7 +56,7 @@
|
||||
- (NSString *)nameForGID:(gid_t)gid;
|
||||
- (NSString *)sanitizeString:(NSString *)inStr;
|
||||
- (NSString *)serialForDevice:(NSString *)devPath;
|
||||
- (NSString *)originalPathForTranslocation:(santa_message_t)message;
|
||||
- (NSString *)originalPathForTranslocation:(const santa_message_t *)message;
|
||||
|
||||
// A cache for usernames and groups.
|
||||
@property(readonly, nonatomic) NSCache<NSNumber *, NSString *> *userNameMap;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import "Source/santad/SNTDatabaseController.h"
|
||||
|
||||
#import "Source/santad/Logs/SNTFileEventLog.h"
|
||||
#import "Source/santad/Logs/SNTProtobufEventLog.h"
|
||||
#import "Source/santad/Logs/SNTSyslogEventLog.h"
|
||||
|
||||
@interface SNTEventLog ()
|
||||
@@ -92,7 +93,11 @@
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)writeLog:(NSString *)log {
|
||||
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
- (void)forceFlush {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
@@ -380,7 +385,11 @@
|
||||
Security.framework so that we can still build against the 10.11 SDK. If the path has not been
|
||||
translocated or if running on macOS prior to 10.12, this method returns nil.
|
||||
*/
|
||||
- (NSString *)originalPathForTranslocation:(santa_message_t)message {
|
||||
- (NSString *)originalPathForTranslocation:(const santa_message_t *)message {
|
||||
if (!message) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// The first time this function is called, we attempt to find the addresses of
|
||||
// SecTranslocateIsTranslocatedURL and SecTranslocateCreateOriginalPathForURL inside of the
|
||||
// Security.framework library. If we were successful, handle will be non-NULL and is never
|
||||
@@ -405,7 +414,7 @@
|
||||
if (!IsTranslocatedURL || !CreateOriginalPathForURL) return nil;
|
||||
|
||||
// Determine if the executable URL has been translocated or not.
|
||||
CFURLRef cfExecURL = (__bridge CFURLRef)[NSURL fileURLWithPath:@(message.path)];
|
||||
CFURLRef cfExecURL = (__bridge CFURLRef)[NSURL fileURLWithPath:@(message->path)];
|
||||
bool isTranslocated = false;
|
||||
if (!IsTranslocatedURL(cfExecURL, &isTranslocated, NULL) || !isTranslocated) return nil;
|
||||
|
||||
@@ -413,7 +422,7 @@
|
||||
// launched the executable. So we temporarily drop from root down to this uid, then reset.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
pthread_setugid_np(message.uid, message.gid);
|
||||
pthread_setugid_np(message->uid, message->gid);
|
||||
NSURL *origURL = CFBridgingRelease(CreateOriginalPathForURL(cfExecURL, NULL));
|
||||
pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE);
|
||||
#pragma clang diagnostic pop
|
||||
@@ -422,7 +431,7 @@
|
||||
}
|
||||
|
||||
+ (instancetype)logger {
|
||||
static SNTEventLog *logger;
|
||||
static SNTEventLog *logger = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
switch ([[SNTConfigurator configurator] eventLogType]) {
|
||||
@@ -434,6 +443,10 @@
|
||||
logger = [[SNTFileEventLog alloc] init];
|
||||
break;
|
||||
}
|
||||
case SNTEventLogTypeProtobuf: {
|
||||
logger = [[SNTProtobufEventLog alloc] init];
|
||||
break;
|
||||
}
|
||||
default: logger = nil;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -96,4 +96,10 @@
|
||||
[self.buffer setLength:0];
|
||||
}
|
||||
|
||||
- (void)forceFlush {
|
||||
dispatch_sync(self.q, ^{
|
||||
[self flushBuffer];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
28
Source/santad/Logs/SNTLogOutput.h
Normal file
28
Source/santad/Logs/SNTLogOutput.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/Santa.pbobjc.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol SNTLogOutput<NSObject>
|
||||
|
||||
- (void)logEvent:(SNTPBSantaMessage *)event;
|
||||
- (void)flush;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
21
Source/santad/Logs/SNTProtobufEventLog.h
Normal file
21
Source/santad/Logs/SNTProtobufEventLog.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/Logs/SNTEventLog.h"
|
||||
|
||||
@interface SNTProtobufEventLog : SNTEventLog
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message;
|
||||
|
||||
@end
|
||||
348
Source/santad/Logs/SNTProtobufEventLog.m
Normal file
348
Source/santad/Logs/SNTProtobufEventLog.m
Normal file
@@ -0,0 +1,348 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/Logs/SNTProtobufEventLog.h"
|
||||
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <libproc.h>
|
||||
#import <os/log.h>
|
||||
|
||||
#import "Source/common/SNTAllowlistInfo.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/Santa.pbobjc.h"
|
||||
#import "Source/santad/Logs/SNTSimpleMaildir.h"
|
||||
|
||||
@interface SNTProtobufEventLog ()
|
||||
@property(readonly) id<SNTLogOutput> logOutput;
|
||||
@end
|
||||
|
||||
@implementation SNTProtobufEventLog
|
||||
|
||||
- (instancetype)init {
|
||||
SNTSimpleMaildir *mailDir = [[SNTSimpleMaildir alloc]
|
||||
initWithBaseDirectory:[[SNTConfigurator configurator] mailDirectory]
|
||||
filenamePrefix:@"out.log"
|
||||
fileSizeThreshold:[[SNTConfigurator configurator] mailDirectoryFileSizeThresholdKB] * 1024
|
||||
directorySizeThreshold:[[SNTConfigurator configurator] mailDirectorySizeThresholdMB] * 1024 *
|
||||
1024
|
||||
maxTimeBetweenFlushes:[[SNTConfigurator configurator] mailDirectoryEventMaxFlushTimeSec]];
|
||||
return [self initWithLog:mailDir];
|
||||
}
|
||||
|
||||
- (instancetype)initWithLog:(id<SNTLogOutput>)log {
|
||||
if (!log) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_logOutput = log;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)forceFlush {
|
||||
[self.logOutput flush];
|
||||
}
|
||||
|
||||
- (void)logWithSantaMessage:(santa_message_t *)santaMsg
|
||||
wrapper:(void (^)(SNTPBSantaMessage *))setMessage {
|
||||
SNTPBSantaMessage *smpb = [[SNTPBSantaMessage alloc] init];
|
||||
if (santaMsg && santaMsg->es_message) {
|
||||
struct timespec ts = ((es_message_t *)santaMsg->es_message)->time;
|
||||
NSDate *esTime =
|
||||
[NSDate dateWithTimeIntervalSince1970:(double)(ts.tv_sec) + (((double)ts.tv_nsec) / 1E9)];
|
||||
smpb.eventTime = [[GPBTimestamp alloc] initWithDate:esTime];
|
||||
} else {
|
||||
smpb.eventTime = [[GPBTimestamp alloc] initWithDate:[NSDate date]];
|
||||
}
|
||||
setMessage(smpb);
|
||||
[self.logOutput logEvent:smpb];
|
||||
}
|
||||
|
||||
- (void)logWithWrapper:(void (^)(SNTPBSantaMessage *))setMessage {
|
||||
return [self logWithSantaMessage:nil wrapper:setMessage];
|
||||
}
|
||||
|
||||
- (SNTPBFileModification_Action)protobufActionForSantaMessageAction:(santa_action_t)action {
|
||||
switch (action) {
|
||||
case ACTION_NOTIFY_DELETE: return SNTPBFileModification_Action_ActionDelete;
|
||||
case ACTION_NOTIFY_EXCHANGE: return SNTPBFileModification_Action_ActionExchange;
|
||||
case ACTION_NOTIFY_LINK: return SNTPBFileModification_Action_ActionLink;
|
||||
case ACTION_NOTIFY_RENAME: return SNTPBFileModification_Action_ActionRename;
|
||||
case ACTION_NOTIFY_WRITE: return SNTPBFileModification_Action_ActionWrite;
|
||||
default: return SNTPBFileModification_Action_ActionUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)newpathForSantaMessage:(santa_message_t *)message {
|
||||
if (!message) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
switch (message->action) {
|
||||
case ACTION_NOTIFY_EXCHANGE: OS_FALLTHROUGH;
|
||||
case ACTION_NOTIFY_LINK: OS_FALLTHROUGH;
|
||||
case ACTION_NOTIFY_RENAME: return @(message->newpath);
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)processPathForSantaMessage:(santa_message_t *)message {
|
||||
if (!message) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// If we have an ES message, use the path provided by the ES framework.
|
||||
// Otherwise, attempt to lookup the path. Note that this will fail if the
|
||||
// process being queried has already exited.
|
||||
if (message->es_message) {
|
||||
switch (message->action) {
|
||||
case ACTION_NOTIFY_DELETE: OS_FALLTHROUGH;
|
||||
case ACTION_NOTIFY_EXCHANGE: OS_FALLTHROUGH;
|
||||
case ACTION_NOTIFY_LINK: OS_FALLTHROUGH;
|
||||
case ACTION_NOTIFY_RENAME: OS_FALLTHROUGH;
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
return @(((es_message_t *)message->es_message)->process->executable->path.data);
|
||||
}
|
||||
default: return nil;
|
||||
}
|
||||
} else {
|
||||
char path[PATH_MAX];
|
||||
path[0] = '\0';
|
||||
proc_pidpath(message->pid, path, sizeof(path));
|
||||
return @(path);
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTPBProcessInfo *)protobufProcessInfoForSantaMessage:(santa_message_t *)message {
|
||||
if (!message) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
SNTPBProcessInfo *procInfo = [[SNTPBProcessInfo alloc] init];
|
||||
|
||||
procInfo.pid = message->pid;
|
||||
procInfo.pidversion = message->pidversion;
|
||||
procInfo.ppid = message->ppid;
|
||||
procInfo.uid = message->uid;
|
||||
procInfo.gid = message->gid;
|
||||
|
||||
procInfo.user = [self nameForUID:message->uid];
|
||||
procInfo.group = [self nameForGID:message->gid];
|
||||
|
||||
return procInfo;
|
||||
}
|
||||
|
||||
- (SNTPBExecution_Decision)protobufDecisionForCachedDecision:(SNTCachedDecision *)cd {
|
||||
if (cd.decision & SNTEventStateBlock) {
|
||||
return SNTPBExecution_Decision_DecisionDeny;
|
||||
} else if (cd.decision & SNTEventStateAllow) {
|
||||
return SNTPBExecution_Decision_DecisionAllow;
|
||||
} else {
|
||||
return SNTPBExecution_Decision_DecisionUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTPBExecution_Reason)protobufReasonForCachedDecision:(SNTCachedDecision *)cd {
|
||||
switch (cd.decision) {
|
||||
case SNTEventStateAllowBinary: return SNTPBExecution_Reason_ReasonBinary;
|
||||
case SNTEventStateAllowCompiler: return SNTPBExecution_Reason_ReasonCompiler;
|
||||
case SNTEventStateAllowTransitive: return SNTPBExecution_Reason_ReasonTransitive;
|
||||
case SNTEventStateAllowPendingTransitive: return SNTPBExecution_Reason_ReasonPendingTransitive;
|
||||
case SNTEventStateAllowCertificate: return SNTPBExecution_Reason_ReasonCert;
|
||||
case SNTEventStateAllowScope: return SNTPBExecution_Reason_ReasonScope;
|
||||
case SNTEventStateAllowTeamID: return SNTPBExecution_Reason_ReasonTeamId;
|
||||
case SNTEventStateAllowUnknown: return SNTPBExecution_Reason_ReasonUnknown;
|
||||
case SNTEventStateBlockBinary: return SNTPBExecution_Reason_ReasonBinary;
|
||||
case SNTEventStateBlockCertificate: return SNTPBExecution_Reason_ReasonCert;
|
||||
case SNTEventStateBlockScope: return SNTPBExecution_Reason_ReasonScope;
|
||||
case SNTEventStateBlockTeamID: return SNTPBExecution_Reason_ReasonTeamId;
|
||||
case SNTEventStateBlockUnknown: return SNTPBExecution_Reason_ReasonUnknown;
|
||||
|
||||
case SNTEventStateAllow: OS_FALLTHROUGH;
|
||||
case SNTEventStateUnknown: OS_FALLTHROUGH;
|
||||
case SNTEventStateBundleBinary: OS_FALLTHROUGH;
|
||||
case SNTEventStateBlock: return SNTPBExecution_Reason_ReasonNotRunning;
|
||||
}
|
||||
|
||||
return SNTPBExecution_Reason_ReasonUnknown;
|
||||
}
|
||||
|
||||
- (SNTPBExecution_Mode)protobufModeForClientMode:(SNTClientMode)mode {
|
||||
switch (mode) {
|
||||
case SNTClientModeMonitor: return SNTPBExecution_Mode_ModeMonitor;
|
||||
case SNTClientModeLockdown: return SNTPBExecution_Mode_ModeLockdown;
|
||||
case SNTClientModeUnknown: return SNTPBExecution_Mode_ModeUnknown;
|
||||
}
|
||||
return SNTPBExecution_Mode_ModeUnknown;
|
||||
}
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message {
|
||||
SNTPBFileModification *fileMod = [[SNTPBFileModification alloc] init];
|
||||
|
||||
fileMod.action = [self protobufActionForSantaMessageAction:message.action];
|
||||
fileMod.path = @(message.path);
|
||||
fileMod.newpath = [self newpathForSantaMessage:&message];
|
||||
fileMod.process = @(message.pname);
|
||||
fileMod.processPath = [self processPathForSantaMessage:&message];
|
||||
fileMod.processInfo = [self protobufProcessInfoForSantaMessage:&message];
|
||||
fileMod.machineId =
|
||||
[[SNTConfigurator configurator] enableMachineIDDecoration] ? self.machineID : nil;
|
||||
|
||||
[self logWithSantaMessage:&message
|
||||
wrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.fileModification = fileMod;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)logExecution:(santa_message_t)message withDecision:(SNTCachedDecision *)cd {
|
||||
SNTPBExecution *exec = [[SNTPBExecution alloc] init];
|
||||
exec.decision = [self protobufDecisionForCachedDecision:cd];
|
||||
exec.reason = [self protobufReasonForCachedDecision:cd];
|
||||
exec.explain = cd.decisionExtra;
|
||||
exec.sha256 = cd.sha256;
|
||||
exec.certSha256 = cd.certSHA256;
|
||||
exec.certCn = cd.certCommonName;
|
||||
exec.quarantineURL = cd.quarantineURL;
|
||||
exec.processInfo = [self protobufProcessInfoForSantaMessage:&message];
|
||||
exec.mode = [self protobufModeForClientMode:[[SNTConfigurator configurator] clientMode]];
|
||||
exec.path = @(message.path);
|
||||
exec.originalPath = [self originalPathForTranslocation:&message];
|
||||
exec.argsArray = [(__bridge NSArray *)message.args_array mutableCopy];
|
||||
exec.machineId =
|
||||
[[SNTConfigurator configurator] enableMachineIDDecoration] ? self.machineID : nil;
|
||||
|
||||
[self logWithSantaMessage:&message
|
||||
wrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.execution = exec;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message {
|
||||
[self logExecution:message withDecision:cd];
|
||||
}
|
||||
|
||||
- (void)logAllowedExecution:(santa_message_t)message {
|
||||
SNTCachedDecision *cd = [self cachedDecisionForMessage:message];
|
||||
[self logExecution:message withDecision:cd];
|
||||
|
||||
// We also reset the timestamp for transitive rules here, because it happens to be where we
|
||||
// have access to both the execution notification and the sha256 associated with rule.
|
||||
[self resetTimestampForCachedDecision:cd];
|
||||
}
|
||||
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties {
|
||||
NSString *dmgPath = nil;
|
||||
NSString *serial = nil;
|
||||
if ([diskProperties[@"DADeviceModel"] isEqual:@"Disk Image"]) {
|
||||
dmgPath = [self diskImageForDevice:diskProperties[@"DADevicePath"]];
|
||||
} else {
|
||||
serial = [self serialForDevice:diskProperties[@"DADevicePath"]];
|
||||
serial = [serial stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
}
|
||||
|
||||
NSString *model = [NSString stringWithFormat:@"%@ %@", diskProperties[@"DADeviceVendor"] ?: @"",
|
||||
diskProperties[@"DADeviceModel"] ?: @""];
|
||||
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
NSString *appearanceDateString = [self.dateFormatter
|
||||
stringFromDate:[NSDate
|
||||
dateWithTimeIntervalSinceReferenceDate:[diskProperties[@"DAAppearanceTime"]
|
||||
doubleValue]]];
|
||||
|
||||
SNTPBDiskAppeared *diskAppeared = [[SNTPBDiskAppeared alloc] init];
|
||||
diskAppeared.mount = [diskProperties[@"DAVolumePath"] path];
|
||||
diskAppeared.volume = diskProperties[@"DAVolumeName"];
|
||||
diskAppeared.bsdName = diskProperties[@"DAMediaBSDName"];
|
||||
diskAppeared.fs = diskProperties[@"DAVolumeKind"];
|
||||
diskAppeared.model = model;
|
||||
diskAppeared.serial = serial;
|
||||
diskAppeared.bus = diskProperties[@"DADeviceProtocol"];
|
||||
diskAppeared.dmgPath = dmgPath;
|
||||
diskAppeared.appearance = appearanceDateString;
|
||||
|
||||
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.diskAppeared = diskAppeared;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {
|
||||
SNTPBDiskDisappeared *diskDisappeared = [[SNTPBDiskDisappeared alloc] init];
|
||||
|
||||
diskDisappeared.mount = [diskProperties[@"DAVolumePath"] path];
|
||||
diskDisappeared.volume = diskProperties[@"DAVolumeName"];
|
||||
diskDisappeared.bsdName = diskProperties[@"DAMediaBSDName"];
|
||||
|
||||
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.diskDisappeared = diskDisappeared;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events {
|
||||
for (SNTStoredEvent *event in events) {
|
||||
SNTPBBundle *bundle = [[SNTPBBundle alloc] init];
|
||||
|
||||
bundle.sha256 = event.fileSHA256;
|
||||
bundle.bundleHash = event.fileBundleHash;
|
||||
bundle.bundleName = event.fileBundleName;
|
||||
bundle.bundleId = event.fileBundleID;
|
||||
bundle.bundlePath = event.fileBundlePath;
|
||||
bundle.path = event.filePath;
|
||||
|
||||
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.bundle = bundle;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logFork:(santa_message_t)message {
|
||||
SNTPBFork *forkEvent = [[SNTPBFork alloc] init];
|
||||
|
||||
forkEvent.processInfo = [self protobufProcessInfoForSantaMessage:&message];
|
||||
|
||||
[self logWithSantaMessage:&message
|
||||
wrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.fork = forkEvent;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)logExit:(santa_message_t)message {
|
||||
SNTPBExit *exitEvent = [[SNTPBExit alloc] init];
|
||||
|
||||
exitEvent.processInfo = [self protobufProcessInfoForSantaMessage:&message];
|
||||
|
||||
[self logWithSantaMessage:&message
|
||||
wrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.exit = exitEvent;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo {
|
||||
SNTPBAllowlist *allowlistEvent = [[SNTPBAllowlist alloc] init];
|
||||
|
||||
allowlistEvent.pid = allowlistInfo.pid;
|
||||
allowlistEvent.pidversion = allowlistInfo.pidversion;
|
||||
allowlistEvent.path = allowlistInfo.targetPath;
|
||||
allowlistEvent.sha256 = allowlistInfo.sha256;
|
||||
|
||||
[self logWithWrapper:^(SNTPBSantaMessage *sm) {
|
||||
sm.allowlist = allowlistEvent;
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
438
Source/santad/Logs/SNTProtobufEventLogTest.m
Normal file
438
Source/santad/Logs/SNTProtobufEventLogTest.m
Normal file
@@ -0,0 +1,438 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <grp.h>
|
||||
#import <pwd.h>
|
||||
|
||||
#import "Source/common/SNTAllowlistInfo.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/santad/EventProviders/EndpointSecurityTestUtil.h"
|
||||
#import "Source/santad/Logs/SNTProtobufEventLog.h"
|
||||
#import "Source/santad/Logs/SNTSimpleMaildir.h"
|
||||
|
||||
@interface SNTProtobufEventLogTest : XCTestCase
|
||||
@property id mockConfigurator;
|
||||
@property id mockLogOutput;
|
||||
@end
|
||||
|
||||
@interface SNTProtobufEventLog (Testing)
|
||||
- (instancetype)initWithLog:(id<SNTLogOutput>)log;
|
||||
@end
|
||||
|
||||
NSString *getBestBundleName(NSBundle *bundle) {
|
||||
NSDictionary *infoDict = [bundle infoDictionary];
|
||||
return [[infoDict objectForKey:@"CFBundleDisplayName"] description]
|
||||
?: [[infoDict objectForKey:@"CFBundleName"] description];
|
||||
}
|
||||
|
||||
SNTStoredEvent *createTestBundleStoredEvent(NSBundle *bundle, NSString *fakeBundleHash,
|
||||
NSString *fakeFileHash) {
|
||||
if (!bundle) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
SNTStoredEvent *event = [[SNTStoredEvent alloc] init];
|
||||
|
||||
event.idx = @(arc4random());
|
||||
event.fileSHA256 = fakeFileHash;
|
||||
event.fileBundleHash = fakeBundleHash;
|
||||
event.fileBundleName = getBestBundleName(bundle);
|
||||
event.fileBundleID = bundle.bundleIdentifier;
|
||||
event.filePath = bundle.executablePath;
|
||||
event.fileBundlePath = bundle.bundlePath;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
id getEventForMessage(SNTPBSantaMessage *santaMsg, SNTPBSantaMessage_Message_OneOfCase expectedCase,
|
||||
NSString *propertyName, Class expectedClass) {
|
||||
if (santaMsg.messageOneOfCase != expectedCase) {
|
||||
LOGE(@"Unexpected message type. Had: %d, Expected: %d", santaMsg.messageOneOfCase,
|
||||
expectedCase);
|
||||
return nil;
|
||||
}
|
||||
|
||||
id event = [santaMsg valueForKey:propertyName];
|
||||
XCTAssertTrue([event isKindOfClass:expectedClass], "Extracted unexpected class");
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
NSBundle *getBundleForSystemApplication(NSString *appName) {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
return
|
||||
[NSBundle bundleWithPath:[NSString stringWithFormat:@"/System/Applications/%@", appName]];
|
||||
} else {
|
||||
return [NSBundle bundleWithPath:[NSString stringWithFormat:@"/Applications/%@", appName]];
|
||||
}
|
||||
}
|
||||
|
||||
void assertProcessInfoMatchesExpected(SNTPBProcessInfo *procInfo, const santa_message_t *expected) {
|
||||
XCTAssertTrue(procInfo.pid == expected->pid);
|
||||
XCTAssertTrue(procInfo.pidversion == expected->pidversion);
|
||||
XCTAssertTrue(procInfo.ppid == expected->ppid);
|
||||
XCTAssertTrue(procInfo.uid == expected->uid);
|
||||
XCTAssertTrue(procInfo.gid == expected->gid);
|
||||
XCTAssertTrue([procInfo.user isEqualToString:@(user_from_uid(expected->uid, 0))]);
|
||||
XCTAssertTrue([procInfo.group isEqualToString:@(group_from_gid(expected->gid, 0))]);
|
||||
}
|
||||
|
||||
// Creates a basic santa message with only process-related info filled out.
|
||||
// Adding path data is left as an exercise to the caller.
|
||||
santa_message_t getBasicSantaMessage(santa_action_t action) {
|
||||
santa_message_t santaMsg = {0};
|
||||
|
||||
santaMsg.action = action;
|
||||
santaMsg.uid = 242;
|
||||
santaMsg.gid = 20;
|
||||
santaMsg.pid = arc4random() % 1000;
|
||||
santaMsg.pidversion = arc4random() % 1000;
|
||||
santaMsg.ppid = arc4random() % 1000;
|
||||
|
||||
return santaMsg;
|
||||
}
|
||||
|
||||
@implementation SNTProtobufEventLogTest
|
||||
|
||||
- (void)setUp {
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
|
||||
OCMStub([self.mockConfigurator enableMachineIDDecoration]).andReturn(NO);
|
||||
|
||||
self.mockLogOutput = OCMStrictProtocolMock(@protocol(SNTLogOutput));
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
[self.mockConfigurator stopMocking];
|
||||
[self.mockLogOutput stopMocking];
|
||||
}
|
||||
|
||||
- (void)testLogFileModification {
|
||||
NSString *processName = @"launchd";
|
||||
NSString *processPath = @"/sbin/launchd";
|
||||
NSString *sourcePath = @"/foo/bar.txt";
|
||||
NSString *targetPath = @"/bar/foo.txt";
|
||||
struct timespec ts = {123, 456};
|
||||
|
||||
// Create a test ES message with some important data set
|
||||
es_file_t esFile = MakeESFile([processPath UTF8String]);
|
||||
es_process_t esProc = MakeESProcess(&esFile);
|
||||
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_RENAME, &esProc, ts);
|
||||
|
||||
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_RENAME);
|
||||
strlcpy(santaMsg.path, [sourcePath UTF8String], sizeof(santaMsg.path));
|
||||
strlcpy(santaMsg.newpath, [targetPath UTF8String], sizeof(santaMsg.newpath));
|
||||
strlcpy(santaMsg.pname, [processName UTF8String], sizeof(santaMsg.pname));
|
||||
santaMsg.args_array = nil;
|
||||
santaMsg.es_message = &esMsg;
|
||||
|
||||
OCMExpect([self.mockLogOutput logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
// Note: Only checking seconds because nano conversion can drift
|
||||
// slightly due to double precision.
|
||||
if (sm.eventTime.seconds != ts.tv_sec) {
|
||||
LOGE(@"Unexpected message event time");
|
||||
return NO;
|
||||
}
|
||||
|
||||
SNTPBFileModification *fileMod = getEventForMessage(
|
||||
sm, SNTPBSantaMessage_Message_OneOfCase_FileModification,
|
||||
@"fileModification", [SNTPBFileModification class]);
|
||||
|
||||
if (fileMod.action != SNTPBFileModification_Action_ActionRename ||
|
||||
![fileMod.path isEqualToString:sourcePath] ||
|
||||
![fileMod.newpath isEqualToString:targetPath] ||
|
||||
![fileMod.process isEqualToString:processName] ||
|
||||
![fileMod.processPath isEqualToString:processPath] ||
|
||||
!fileMod.hasProcessInfo || fileMod.processInfo == nil ||
|
||||
[fileMod.machineId length] != 0) {
|
||||
LOGE(@"Unexpected file modification data");
|
||||
return NO;
|
||||
}
|
||||
|
||||
assertProcessInfoMatchesExpected(fileMod.processInfo, &santaMsg);
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
[eventLog logFileModification:santaMsg];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
|
||||
}
|
||||
|
||||
- (void)testLogExecutionDenied {
|
||||
NSString *processName = @"launchd";
|
||||
NSString *processPath = @"/sbin/launchd";
|
||||
NSArray *execArgs = @[ @"/sbin/launchd", @"--init", @"--testing" ];
|
||||
NSString *explanation = @"explanation";
|
||||
NSString *sha256 = @"a4587ab1e705a3804fd23c387a7bc1b39505f699eca35f57687809f8a7031d0f";
|
||||
NSString *certSHA256 = @"293d4a40b539dfddf9e011fb0e37f19aa86c96aad2e4bf481aac9e50487f3868";
|
||||
NSString *commonName = @"my cert common name";
|
||||
NSString *quarantineURL = @"http://localhost/quarantine";
|
||||
|
||||
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_EXEC);
|
||||
strlcpy(santaMsg.path, [processPath UTF8String], sizeof(santaMsg.path));
|
||||
santaMsg.newpath[0] = '\0';
|
||||
strlcpy(santaMsg.pname, [processName UTF8String], sizeof(santaMsg.pname));
|
||||
santaMsg.args_array = (__bridge void *)execArgs;
|
||||
santaMsg.es_message = nil;
|
||||
|
||||
SNTCachedDecision *cachedDecision = [[SNTCachedDecision alloc] init];
|
||||
cachedDecision.decision = SNTEventStateBlockTeamID;
|
||||
cachedDecision.decisionExtra = explanation;
|
||||
cachedDecision.sha256 = sha256;
|
||||
cachedDecision.certSHA256 = certSHA256;
|
||||
cachedDecision.certCommonName = commonName;
|
||||
cachedDecision.quarantineURL = quarantineURL;
|
||||
|
||||
OCMExpect([self.mockLogOutput
|
||||
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
SNTPBExecution *exec = getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_Execution,
|
||||
@"execution", [SNTPBExecution class]);
|
||||
|
||||
if (exec.decision != SNTPBExecution_Decision_DecisionDeny ||
|
||||
exec.reason != SNTPBExecution_Reason_ReasonTeamId ||
|
||||
![exec.explain isEqualToString:explanation] || ![exec.sha256 isEqualToString:sha256] ||
|
||||
![exec.certSha256 isEqualToString:certSHA256] ||
|
||||
![exec.certCn isEqualToString:commonName] ||
|
||||
![exec.quarantineURL isEqualToString:quarantineURL] || !exec.hasProcessInfo ||
|
||||
exec.processInfo == nil || exec.mode != SNTPBExecution_Mode_ModeLockdown ||
|
||||
![exec.path isEqualToString:processPath] || [exec.originalPath length] != 0 ||
|
||||
![exec.argsArray isEqualToArray:execArgs] || [exec.machineId length] != 0) {
|
||||
LOGE(@"Unexpected execution data");
|
||||
return NO;
|
||||
}
|
||||
|
||||
assertProcessInfoMatchesExpected(exec.processInfo, &santaMsg);
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
[eventLog logDeniedExecution:cachedDecision withMessage:santaMsg];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
|
||||
}
|
||||
|
||||
- (void)testLogDiskAppeared {
|
||||
NSString *mount = @"/mnt/appear";
|
||||
NSString *volume = @"Macintosh HD";
|
||||
NSString *bsdName = @"disk0s1";
|
||||
NSString *kind = @"apfs";
|
||||
NSString *deviceVendor = @"gCorp";
|
||||
NSString *deviceModel = @"G1";
|
||||
NSString *serial = @"fake_serial";
|
||||
NSString *devicePath = @"IODeviceTree:/";
|
||||
NSString *deviceProto = @"USB";
|
||||
NSString *appeared = @"2001-01-01T00:00:00.000Z";
|
||||
|
||||
NSDictionary *diskProperties = @{
|
||||
@"DAVolumePath" : [NSURL URLWithString:mount],
|
||||
@"DAVolumeName" : volume,
|
||||
@"DAMediaBSDName" : bsdName,
|
||||
@"DAVolumeKind" : kind,
|
||||
@"DADeviceVendor" : deviceVendor,
|
||||
@"DADeviceModel" : deviceModel,
|
||||
@"DADevicePath" : devicePath,
|
||||
@"DADeviceProtocol" : deviceProto,
|
||||
};
|
||||
|
||||
OCMExpect([self.mockLogOutput
|
||||
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
SNTPBDiskAppeared *diskAppeared =
|
||||
getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_DiskAppeared, @"diskAppeared",
|
||||
[SNTPBDiskAppeared class]);
|
||||
|
||||
if (![diskAppeared.mount isEqualToString:mount] ||
|
||||
![diskAppeared.volume isEqualToString:volume] ||
|
||||
![diskAppeared.bsdName isEqualToString:bsdName] ||
|
||||
![diskAppeared.fs isEqualToString:kind] ||
|
||||
![diskAppeared.model
|
||||
isEqualToString:[NSString stringWithFormat:@"%@ %@", deviceVendor, deviceModel]] ||
|
||||
![diskAppeared.serial isEqualToString:serial] ||
|
||||
![diskAppeared.bus isEqualToString:deviceProto] || [diskAppeared.dmgPath length] != 0 ||
|
||||
![diskAppeared.appearance isEqualToString:appeared]) {
|
||||
LOGE(@"Unexpected disk appeared data");
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
|
||||
id eventLogMock = OCMPartialMock(eventLog);
|
||||
OCMExpect([eventLogMock serialForDevice:[OCMArg checkWithBlock:^BOOL(NSString *path) {
|
||||
return [path isEqualToString:devicePath];
|
||||
}]])
|
||||
.andReturn(serial);
|
||||
|
||||
[eventLog logDiskAppeared:diskProperties];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput) && OCMVerifyAll(eventLogMock),
|
||||
"Unable to verify all expectations");
|
||||
[eventLogMock stopMocking];
|
||||
}
|
||||
|
||||
- (void)testLogDiskDisappeared {
|
||||
NSString *mount = @"/mnt/disappear";
|
||||
NSString *volume = @"Macintosh HD";
|
||||
NSString *bsdName = @"disk0s2";
|
||||
|
||||
NSDictionary *diskProperties = @{
|
||||
@"DAVolumePath" : [NSURL URLWithString:mount],
|
||||
@"DAVolumeName" : volume,
|
||||
@"DAMediaBSDName" : bsdName,
|
||||
};
|
||||
|
||||
OCMExpect([self.mockLogOutput logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
SNTPBDiskDisappeared *diskDisappeared = getEventForMessage(
|
||||
sm, SNTPBSantaMessage_Message_OneOfCase_DiskDisappeared,
|
||||
@"diskDisappeared", [SNTPBDiskDisappeared class]);
|
||||
|
||||
if (![diskDisappeared.mount isEqualToString:mount] ||
|
||||
![diskDisappeared.volume isEqualToString:volume] ||
|
||||
![diskDisappeared.bsdName isEqualToString:bsdName]) {
|
||||
LOGE(@"Unexpected disk disappeared data");
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
[eventLog logDiskDisappeared:diskProperties];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
|
||||
}
|
||||
|
||||
- (void)testLogBundleHashingEvents {
|
||||
NSArray<SNTStoredEvent *> *storedEvents = @[
|
||||
createTestBundleStoredEvent(getBundleForSystemApplication(@"Calculator.app"), @"abc123",
|
||||
@"xyz456"),
|
||||
createTestBundleStoredEvent(getBundleForSystemApplication(@"Calendar.app"), @"123abc",
|
||||
@"456xyz"),
|
||||
];
|
||||
|
||||
for (SNTStoredEvent *storedEvent in storedEvents) {
|
||||
OCMExpect([self.mockLogOutput
|
||||
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
SNTPBBundle *bundleEvent = getEventForMessage(
|
||||
sm, SNTPBSantaMessage_Message_OneOfCase_Bundle, @"bundle", [SNTPBBundle class]);
|
||||
|
||||
if (![bundleEvent.sha256 isEqualToString:storedEvent.fileSHA256] ||
|
||||
![bundleEvent.bundleHash isEqualToString:storedEvent.fileBundleHash] ||
|
||||
![bundleEvent.bundleName isEqualToString:storedEvent.fileBundleName] ||
|
||||
![bundleEvent.bundleId isEqualToString:storedEvent.fileBundleID] ||
|
||||
![bundleEvent.bundlePath isEqualToString:storedEvent.fileBundlePath] ||
|
||||
![bundleEvent.path isEqualToString:storedEvent.filePath]) {
|
||||
LOGE(@"Unexpected bundle event data for: %@", storedEvent.filePath);
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
}
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
[eventLog logBundleHashingEvents:storedEvents];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
|
||||
}
|
||||
|
||||
- (void)testLogFork {
|
||||
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_FORK);
|
||||
|
||||
OCMExpect([self.mockLogOutput
|
||||
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
SNTPBFork *forkEvent = getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_Fork,
|
||||
@"fork", [SNTPBFork class]);
|
||||
|
||||
if (!forkEvent.hasProcessInfo || forkEvent.processInfo == nil) {
|
||||
LOGE(@"Unexpected fork data");
|
||||
return NO;
|
||||
}
|
||||
|
||||
assertProcessInfoMatchesExpected(forkEvent.processInfo, &santaMsg);
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
[eventLog logFork:santaMsg];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
|
||||
}
|
||||
|
||||
- (void)testLogExit {
|
||||
santa_message_t santaMsg = getBasicSantaMessage(ACTION_NOTIFY_EXIT);
|
||||
|
||||
OCMExpect([self.mockLogOutput
|
||||
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
SNTPBExit *exitEvent = getEventForMessage(sm, SNTPBSantaMessage_Message_OneOfCase_Exit,
|
||||
@"exit", [SNTPBExit class]);
|
||||
|
||||
if (!exitEvent.hasProcessInfo || exitEvent.processInfo == nil) {
|
||||
LOGE(@"Unexpected exit data");
|
||||
return NO;
|
||||
}
|
||||
|
||||
assertProcessInfoMatchesExpected(exitEvent.processInfo, &santaMsg);
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
[eventLog logExit:santaMsg];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
|
||||
}
|
||||
|
||||
- (void)testLogAllowlist {
|
||||
SNTAllowlistInfo *allowlistInfo = [[SNTAllowlistInfo alloc] initWithPid:123
|
||||
pidversion:456
|
||||
targetPath:@"/sbin/launchd"
|
||||
sha256:@"abc123"];
|
||||
|
||||
OCMExpect([self.mockLogOutput
|
||||
logEvent:[OCMArg checkWithBlock:^BOOL(SNTPBSantaMessage *sm) {
|
||||
SNTPBAllowlist *allowlistEvent = getEventForMessage(
|
||||
sm, SNTPBSantaMessage_Message_OneOfCase_Allowlist, @"allowlist", [SNTPBAllowlist class]);
|
||||
|
||||
if (allowlistEvent.pid != allowlistInfo.pid ||
|
||||
allowlistEvent.pidversion != allowlistInfo.pidversion ||
|
||||
![allowlistEvent.path isEqualToString:allowlistInfo.targetPath] ||
|
||||
![allowlistEvent.sha256 isEqualToString:allowlistInfo.sha256]) {
|
||||
LOGE(@"Unexpected allowlist data");
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}]]);
|
||||
|
||||
SNTProtobufEventLog *eventLog = [[SNTProtobufEventLog alloc] initWithLog:self.mockLogOutput];
|
||||
[eventLog logAllowlist:allowlistInfo];
|
||||
|
||||
XCTAssertTrue(OCMVerifyAll(self.mockLogOutput), "Unable to verify all expectations");
|
||||
}
|
||||
|
||||
@end
|
||||
39
Source/santad/Logs/SNTSimpleMaildir.h
Normal file
39
Source/santad/Logs/SNTSimpleMaildir.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/Santa.pbobjc.h"
|
||||
#import "Source/santad/Logs/SNTLogOutput.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SNTSimpleMaildir : NSObject<SNTLogOutput>
|
||||
|
||||
- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory
|
||||
filenamePrefix:(NSString *)filenamePrefix
|
||||
fileSizeThreshold:(size_t)fileSiszeThreshold
|
||||
directorySizeThreshold:(size_t)directorySizeThreshold
|
||||
maxTimeBetweenFlushes:(NSTimeInterval)maxTimeBetweenFlushes
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (void)logEvent:(SNTPBSantaMessage *)message;
|
||||
- (void)flush;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
396
Source/santad/Logs/SNTSimpleMaildir.m
Normal file
396
Source/santad/Logs/SNTSimpleMaildir.m
Normal file
@@ -0,0 +1,396 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/Logs/SNTSimpleMaildir.h"
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
#include <malloc/malloc.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTMetricSet.h"
|
||||
|
||||
static NSString *kDefaultMetricFieldName = @"result";
|
||||
static NSString *kErrorUserInfoKey = @"MetricsFieldName";
|
||||
|
||||
/** Helper for creating errors. */
|
||||
static NSError *MakeError(NSString *description) {
|
||||
return [NSError errorWithDomain:@"com.google.santa"
|
||||
code:1
|
||||
userInfo:@{kErrorUserInfoKey : description}];
|
||||
}
|
||||
|
||||
static NSString *ErrorToMetricFieldName(NSError *error) {
|
||||
if (!error) {
|
||||
return kDefaultMetricFieldName;
|
||||
}
|
||||
|
||||
return error.userInfo[kErrorUserInfoKey]
|
||||
?: [NSString stringWithFormat:@"%@:%d", error.domain, (int)error.code];
|
||||
}
|
||||
|
||||
static size_t SNTRoundUpToNextPage(size_t size) {
|
||||
const size_t pageSize = 4096;
|
||||
|
||||
if (size % pageSize == 0) {
|
||||
return size;
|
||||
}
|
||||
return pageSize * ((size / pageSize) + 1);
|
||||
}
|
||||
|
||||
static uint64_t getUptimeSeconds() {
|
||||
static dispatch_once_t onceToken;
|
||||
static mach_timebase_info_data_t info;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
mach_timebase_info(&info);
|
||||
});
|
||||
|
||||
uint64_t cur = mach_absolute_time();
|
||||
|
||||
// Convert from nanoseconds to seconds
|
||||
return cur * info.numer / (1000 * 1000 * 1000 * info.denom);
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation SNTSimpleMaildir {
|
||||
/** The prefix to use for new log files. */
|
||||
NSString *_filenamePrefix;
|
||||
|
||||
/** The base, tmp and new directory for spooling files. */
|
||||
NSString *_baseDirectory;
|
||||
NSString *_tmpDirectory;
|
||||
NSString *_newDirectory;
|
||||
|
||||
/**
|
||||
* Timer that flushes every `maxTimeBetweenFlushes` seconds.
|
||||
* Used to avoid excessive latency exporting events.
|
||||
*/
|
||||
NSTimer *_flushTimer;
|
||||
|
||||
/** The size threshold after which to start a new log file. */
|
||||
size_t _fileSizeThreshold;
|
||||
|
||||
/** Threshold for the estimated spool size. */
|
||||
size_t _spoolSizeThreshold;
|
||||
|
||||
/** Temporary storage for SNTPBSantaMessage in an SNTPBLogBatch. */
|
||||
SNTPBLogBatch *_outputProto;
|
||||
|
||||
/** Current serialized size of all events in the _outputProto batch */
|
||||
size_t _outputProtoSerializedSize;
|
||||
|
||||
/** Current size of the file system spooling directory. */
|
||||
size_t _estimatedSpoolSize;
|
||||
|
||||
/** Counter for the files we've already opened. Used to generate file names. */
|
||||
int _createdFileCount;
|
||||
|
||||
/** Dispatch queue to synchronize flush operations */
|
||||
dispatch_queue_t _flushQueue;
|
||||
|
||||
/** Counter for successful and failed event flushing to disk. */
|
||||
SNTMetricCounter *_eventsFlushedCounter;
|
||||
|
||||
/** Counter for successful and failed event queueing in memory. */
|
||||
SNTMetricCounter *_eventsQueuedCounter;
|
||||
|
||||
/** Gauge for the overall spool size, calculated periodically. */
|
||||
SNTMetricInt64Gauge *_spoolSizeGauge;
|
||||
|
||||
/**
|
||||
* Mach absolute time in seconds of the last time the spool size
|
||||
* was calculated.
|
||||
*/
|
||||
uint64_t _lastCalculatedSpoolSizeTime;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBaseDirectory:(NSString *)baseDirectory
|
||||
filenamePrefix:(NSString *)filenamePrefix
|
||||
fileSizeThreshold:(size_t)fileSizeThreshold
|
||||
directorySizeThreshold:(size_t)directorySizeThreshold
|
||||
maxTimeBetweenFlushes:(NSTimeInterval)maxTimeBetweenFlushes {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_baseDirectory = baseDirectory;
|
||||
_tmpDirectory = [baseDirectory stringByAppendingPathComponent:@"tmp"];
|
||||
_newDirectory = [baseDirectory stringByAppendingPathComponent:@"new"];
|
||||
_filenamePrefix = [filenamePrefix copy];
|
||||
_fileSizeThreshold = fileSizeThreshold;
|
||||
_spoolSizeThreshold = directorySizeThreshold;
|
||||
_estimatedSpoolSize = SIZE_T_MAX; // Force a recalculation of the spool directory size
|
||||
_createdFileCount = 0;
|
||||
_outputProto = [[SNTPBLogBatch alloc] init];
|
||||
_outputProtoSerializedSize = 0;
|
||||
_lastCalculatedSpoolSizeTime = 0;
|
||||
|
||||
_eventsFlushedCounter = [[SNTMetricSet sharedInstance]
|
||||
counterWithName:@"/santa/events_flushed"
|
||||
fieldNames:@[ kDefaultMetricFieldName ]
|
||||
helpText:@"Number of events flushed, with the result of the flush operation"];
|
||||
_eventsQueuedCounter = [[SNTMetricSet sharedInstance]
|
||||
counterWithName:@"/santa/events_queued"
|
||||
fieldNames:@[ kDefaultMetricFieldName ]
|
||||
helpText:@"Number of events queued in memory, with the result "
|
||||
@"of their conversion to anyproto"];
|
||||
_spoolSizeGauge =
|
||||
[[SNTMetricSet sharedInstance] int64GaugeWithName:@"/santa/spool_size"
|
||||
fieldNames:@[ kDefaultMetricFieldName ]
|
||||
helpText:@"Snapshot of the current pool size"];
|
||||
|
||||
[[SNTMetricSet sharedInstance] registerCallback:^(void) {
|
||||
// Only calculate spool size for metrics every 5 minutes
|
||||
static const int frequencySecs = 300;
|
||||
|
||||
uint64_t curTime = getUptimeSeconds();
|
||||
|
||||
if (curTime - _lastCalculatedSpoolSizeTime >= frequencySecs) {
|
||||
NSError *err = nil;
|
||||
size_t curSize = [SNTSimpleMaildir spoolDirectorySize:_newDirectory withError:&err];
|
||||
|
||||
if (err) {
|
||||
// Failed to calculate spool size. Try again next time...
|
||||
return;
|
||||
}
|
||||
|
||||
_lastCalculatedSpoolSizeTime = curTime;
|
||||
|
||||
[_spoolSizeGauge set:curSize forFieldValues:@[ kDefaultMetricFieldName ]];
|
||||
}
|
||||
}];
|
||||
|
||||
_flushQueue =
|
||||
dispatch_queue_create("com.google.santa.daemon.mail",
|
||||
dispatch_queue_attr_make_with_qos_class(
|
||||
DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL, QOS_CLASS_DEFAULT, 0));
|
||||
|
||||
typeof(self) __weak weakSelf = self;
|
||||
_flushTimer = [NSTimer scheduledTimerWithTimeInterval:maxTimeBetweenFlushes
|
||||
repeats:YES
|
||||
block:^(NSTimer *_Nonnull timer) {
|
||||
[weakSelf flush];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_flushTimer invalidate];
|
||||
[self flush];
|
||||
}
|
||||
|
||||
/** Fires the flush timer programmatically. Used for testing purposes. */
|
||||
- (void)fireFlushTimer {
|
||||
[_flushTimer fire];
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes out buffered data.
|
||||
*
|
||||
* Returns the number of events that we attempted to flush, and populates the error if that flush
|
||||
* failed.
|
||||
*/
|
||||
- (void)flushLockedWithError:(NSError **)error {
|
||||
NSAssert([_outputProto.recordsArray count] < INT_MAX, @"Too many records");
|
||||
|
||||
if ([_outputProto.recordsArray count] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self createSpoolDirectoriesWithError:error]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_estimatedSpoolSize > _spoolSizeThreshold) {
|
||||
_estimatedSpoolSize = [SNTSimpleMaildir spoolDirectorySize:_newDirectory withError:error];
|
||||
if (_estimatedSpoolSize > _spoolSizeThreshold) {
|
||||
if (error) {
|
||||
*error = MakeError(@"file_system_threshold_exceeded");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *filename = [NSString stringWithFormat:@"%@.%u", _filenamePrefix, _createdFileCount];
|
||||
NSString *exposedFilepath = [_newDirectory stringByAppendingPathComponent:filename];
|
||||
NSString *outputFilepath = [_tmpDirectory stringByAppendingPathComponent:filename];
|
||||
NSOutputStream *outputFile = [NSOutputStream outputStreamToFileAtPath:outputFilepath append:NO];
|
||||
[outputFile open];
|
||||
_createdFileCount++;
|
||||
|
||||
if (!outputFile) {
|
||||
if (error) {
|
||||
*error = MakeError(@"data_loss_on_open");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL writeSuccess = NO;
|
||||
@try {
|
||||
[_outputProto writeToOutputStream:outputFile];
|
||||
writeSuccess = YES;
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"Error while writing to %@: %@", outputFilepath, exception);
|
||||
if (error) {
|
||||
*error = MakeError(@"data_loss_on_write");
|
||||
}
|
||||
}
|
||||
|
||||
[outputFile close];
|
||||
|
||||
if (!writeSuccess) {
|
||||
// Unable to successfully write all data.
|
||||
[[NSFileManager defaultManager] removeItemAtPath:outputFilepath error:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
if (![[NSFileManager defaultManager] moveItemAtPath:outputFilepath
|
||||
toPath:exposedFilepath
|
||||
error:nil]) {
|
||||
// Delete the tmp file if unable to move
|
||||
[[NSFileManager defaultManager] removeItemAtPath:outputFilepath error:nil];
|
||||
if (error) {
|
||||
*error = MakeError(@"data_loss_on_move");
|
||||
}
|
||||
}
|
||||
|
||||
if (error && !*error) {
|
||||
_estimatedSpoolSize += _outputProtoSerializedSize;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)flushAndUpdateCountersLocked {
|
||||
NSError *error = nil;
|
||||
[self flushLockedWithError:&error];
|
||||
[_eventsFlushedCounter incrementBy:[_outputProto.recordsArray count]
|
||||
forFieldValues:@[ ErrorToMetricFieldName(error) ]];
|
||||
|
||||
// Clear output buffer.
|
||||
_outputProto = [[SNTPBLogBatch alloc] init];
|
||||
_outputProtoSerializedSize = 0;
|
||||
}
|
||||
|
||||
- (void)flush {
|
||||
dispatch_sync(_flushQueue, ^{
|
||||
[self flushAndUpdateCountersLocked];
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)createDirectory:(NSString *)dir withError:(NSError **)error {
|
||||
BOOL isDir;
|
||||
// Check if the path exists
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:dir isDirectory:&isDir]) {
|
||||
// If path doesn't exist, attempt to create it
|
||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:dir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil]) {
|
||||
if (error) {
|
||||
*error = MakeError(@"failed_to_create_dir");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
} else if (!isDir) {
|
||||
// The path existed, but it wasn't a directory
|
||||
if (error) {
|
||||
*error = MakeError(@"path_exists_not_directory");
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// If we made it here, the directory was created or already existed
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)createSpoolDirectoriesWithError:(NSError **)error {
|
||||
return [self createDirectory:_baseDirectory withError:error] &&
|
||||
[self createDirectory:_tmpDirectory withError:error] &&
|
||||
[self createDirectory:_newDirectory withError:error];
|
||||
}
|
||||
|
||||
+ (size_t)spoolDirectorySize:(NSString *)newDirectory withError:(NSError **)error {
|
||||
size_t totalSize = 0;
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSError *enumerationError = nil;
|
||||
NSArray<NSString *> *filenames = [fm contentsOfDirectoryAtPath:newDirectory
|
||||
error:&enumerationError];
|
||||
if (enumerationError) {
|
||||
*error = MakeError(@"spool_dir_enumeration_error");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (NSString *filename in filenames) {
|
||||
NSError *attributesError = nil;
|
||||
NSDictionary<NSFileAttributeKey, id> *attributes =
|
||||
[fm attributesOfItemAtPath:[newDirectory stringByAppendingPathComponent:filename]
|
||||
error:&attributesError];
|
||||
if (attributesError) {
|
||||
if (error) {
|
||||
*error = MakeError(@"spool_dir_attribute_retrieval_error");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
totalSize += SNTRoundUpToNextPage([attributes fileSize]);
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
- (void)logEvent:(SNTPBSantaMessage *)event {
|
||||
dispatch_sync(_flushQueue, ^{
|
||||
// Note: The `serializedSize` method is costly. In order to calculate the
|
||||
// size of the `_outputProto` accurately, we need to add both the serialized
|
||||
// size of the new event plus additional overhead incurred from adding the
|
||||
// new event to the current array of events. The `anyObjOverhead` is used
|
||||
// to store the calculated overhead when the first event is logged and the
|
||||
// overhead is then used when calculating `_outputProtoSerializedSize`.
|
||||
static size_t anyObjOverhead = 0;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
if (_outputProtoSerializedSize > _fileSizeThreshold) {
|
||||
[self flushAndUpdateCountersLocked];
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
GPBAny *any = [GPBAny anyWithMessage:event error:nil];
|
||||
if (any) {
|
||||
[_outputProto.recordsArray addObject:any];
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
size_t outputSerializedSize = [_outputProto serializedSize];
|
||||
size_t eventSerializedSize = [event serializedSize];
|
||||
if (outputSerializedSize > eventSerializedSize) {
|
||||
anyObjOverhead = outputSerializedSize - eventSerializedSize;
|
||||
}
|
||||
});
|
||||
|
||||
_outputProtoSerializedSize += [event serializedSize] + anyObjOverhead;
|
||||
} else {
|
||||
error = MakeError(@"enqueue_error");
|
||||
}
|
||||
|
||||
[_eventsQueuedCounter incrementForFieldValues:@[ ErrorToMetricFieldName(error) ]];
|
||||
});
|
||||
}
|
||||
|
||||
/** Intentionally left no-op method for this class. */
|
||||
- (void)logString:(NSString *)logLine {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -17,10 +17,12 @@
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <libproc.h>
|
||||
|
||||
#import "Source/common/SNTAllowlistInfo.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
|
||||
|
||||
@implementation SNTSyslogEventLog
|
||||
|
||||
@@ -66,10 +68,11 @@
|
||||
char ppath[PATH_MAX] = "(null)";
|
||||
if (message.es_message) {
|
||||
es_message_t *m = message.es_message;
|
||||
es_string_token_t path = m->process->executable->path;
|
||||
strlcpy(ppath, path.data, sizeof(ppath));
|
||||
[SNTEndpointSecurityManager populateBufferFromESFile:m->process->executable
|
||||
buffer:ppath
|
||||
size:sizeof(ppath)];
|
||||
} else {
|
||||
proc_pidpath(message.pid, ppath, PATH_MAX);
|
||||
proc_pidpath(message.pid, ppath, sizeof(ppath));
|
||||
}
|
||||
|
||||
[outStr
|
||||
@@ -190,14 +193,14 @@
|
||||
[self sanitizeString:@(message.path)]];
|
||||
|
||||
// Check for app translocation by GateKeeper, and log original path if the case.
|
||||
NSString *originalPath = [self originalPathForTranslocation:message];
|
||||
NSString *originalPath = [self originalPathForTranslocation:&message];
|
||||
if (originalPath) {
|
||||
[outLog appendFormat:@"|origpath=%@", [self sanitizeString:originalPath]];
|
||||
}
|
||||
|
||||
if (logArgs) {
|
||||
if (message.args_array) {
|
||||
NSArray *args = CFBridgingRelease(message.args_array);
|
||||
NSArray *args = (__bridge NSArray *)message.args_array;
|
||||
[outLog appendFormat:@"|args=%@", [args componentsJoinedByString:@" "]];
|
||||
} else {
|
||||
[self addArgsForPid:message.pid toString:outLog];
|
||||
@@ -286,8 +289,19 @@
|
||||
[self writeLog:s];
|
||||
}
|
||||
|
||||
- (void)logAllowList:(SNTAllowlistInfo *)allowlistInfo {
|
||||
[self
|
||||
writeLog:[NSString stringWithFormat:@"action=ALLOWLIST|pid=%d|pidversion=%d|path=%@|sha256=%@",
|
||||
allowlistInfo.pid, allowlistInfo.pidversion,
|
||||
allowlistInfo.targetPath, allowlistInfo.sha256]];
|
||||
}
|
||||
|
||||
- (void)writeLog:(NSString *)log {
|
||||
LOGI(@"%@", log);
|
||||
}
|
||||
|
||||
- (void)forceFlush {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
#import "Source/common/SNTDropRootPrivs.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTMetricSet.h"
|
||||
@@ -30,7 +31,6 @@
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
#import "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
|
||||
#import "Source/santad/EventProviders/SNTDeviceManager.h"
|
||||
#import "Source/santad/EventProviders/SNTDriverManager.h"
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
#import "Source/santad/Logs/SNTEventLog.h"
|
||||
@@ -60,19 +60,12 @@
|
||||
if (self) {
|
||||
SNTConfigurator *configurator = [SNTConfigurator configurator];
|
||||
|
||||
// Choose an event logger.
|
||||
// Locate and connect to driver / SystemExtension
|
||||
if ([configurator enableSystemExtension]) {
|
||||
if ([configurator enableSysxCache]) {
|
||||
LOGI(@"Using CachingEndpointSecurity as event provider.");
|
||||
_eventProvider = [[SNTCachingEndpointSecurityManager alloc] init];
|
||||
} else {
|
||||
LOGI(@"Using EndpointSecurity as event provider.");
|
||||
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
|
||||
}
|
||||
if ([configurator enableSysxCache]) {
|
||||
LOGI(@"Using CachingEndpointSecurity as event provider.");
|
||||
_eventProvider = [[SNTCachingEndpointSecurityManager alloc] init];
|
||||
} else {
|
||||
LOGI(@"Using Kauth as event provider.");
|
||||
_eventProvider = [[SNTDriverManager alloc] init];
|
||||
LOGI(@"Using EndpointSecurity as event provider.");
|
||||
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
|
||||
}
|
||||
|
||||
if (!_eventProvider) {
|
||||
@@ -92,12 +85,26 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
_deviceManager = [[SNTDeviceManager alloc] init];
|
||||
self.deviceManager.blockUSBMount = [configurator blockUSBMount];
|
||||
self.notQueue = [[SNTNotificationQueue alloc] init];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = [configurator blockUSBMount];
|
||||
if ([configurator remountUSBMode] != nil) {
|
||||
self.deviceManager.remountArgs = [configurator remountUSBMode];
|
||||
deviceManager.remountArgs = [configurator remountUSBMode];
|
||||
}
|
||||
|
||||
NSString *deviceBlockMsg = deviceManager.remountArgs != nil
|
||||
? [configurator remountUSBBlockMessage]
|
||||
: [configurator bannedUSBBlockMessage];
|
||||
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
[[self.notQueue.notifierConnection remoteObjectProxy]
|
||||
postUSBBlockNotification:event
|
||||
withCustomMessage:deviceBlockMsg];
|
||||
};
|
||||
|
||||
_deviceManager = deviceManager;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
|
||||
// The filter is reset when santad disconnects from the driver.
|
||||
// Add the default filters.
|
||||
@@ -107,7 +114,6 @@
|
||||
[self.eventProvider fileModificationPrefixFilterAdd:[configurator fileChangesPrefixFilters]];
|
||||
});
|
||||
|
||||
self.notQueue = [[SNTNotificationQueue alloc] init];
|
||||
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
|
||||
|
||||
// Restart santactl if it goes down
|
||||
@@ -150,13 +156,6 @@
|
||||
options:bits
|
||||
context:NULL];
|
||||
|
||||
if (![configurator enableSystemExtension]) {
|
||||
[configurator addObserver:self
|
||||
forKeyPath:NSStringFromSelector(@selector(enableSystemExtension))
|
||||
options:bits
|
||||
context:NULL];
|
||||
}
|
||||
|
||||
// Establish XPC listener for Santa and santactl connections
|
||||
SNTDaemonControlController *dc =
|
||||
[[SNTDaemonControlController alloc] initWithEventProvider:_eventProvider
|
||||
@@ -365,15 +364,6 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
|
||||
LOGI(@"Changed [allow|deny]list regex, flushing cache");
|
||||
[self.eventProvider flushCacheNonRootOnly:NO];
|
||||
}
|
||||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(enableSystemExtension))]) {
|
||||
BOOL new =
|
||||
[ change[newKey] isKindOfClass : [NSNumber class] ] ? [ change[newKey] boolValue ] : NO;
|
||||
BOOL old = [change[oldKey] isKindOfClass:[NSNumber class]] ? [change[oldKey] boolValue] : NO;
|
||||
if (old == NO && new == YES) {
|
||||
LOGI(@"EnableSystemExtension changed NO -> YES");
|
||||
LOGI(@"The penultimate exit.");
|
||||
exit(0);
|
||||
}
|
||||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(exportMetrics))]) {
|
||||
BOOL new = [ change[newKey] boolValue ];
|
||||
BOOL old = [change[oldKey] boolValue];
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
self.mockSNTDatabaseController = OCMClassMock([SNTDatabaseController class]);
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
OCMStub([self.mockConfigurator enableSystemExtension]).andReturn(true);
|
||||
OCMStub([self.mockConfigurator enableSysxCache]).andReturn(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
[super setUp];
|
||||
fclose(stdout);
|
||||
self.mockSNTDatabaseController = OCMClassMock([SNTDatabaseController class]);
|
||||
XCTAssertTrue([[SNTConfigurator configurator] enableSystemExtension]);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
@class SNTDriverManager;
|
||||
@class SNTEventLog;
|
||||
|
||||
@interface SNTCompilerController : NSObject
|
||||
// Designated initializer takes a SNTEventLog instance so that we can
|
||||
// call saveDecisionDetails: to create a fake cached decision for transitive
|
||||
// rule creation requests that are still pending.
|
||||
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager;
|
||||
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider;
|
||||
|
||||
// Whenever an executable file is closed or renamed whitelist the resulting file.
|
||||
// We assume that we have already determined that the writing process was a compiler.
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
#import "Source/santad/SNTCompilerController.h"
|
||||
|
||||
#import "Source/common/SNTAllowlistInfo.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
@@ -60,10 +61,10 @@
|
||||
- (void)createTransitiveRule:(santa_message_t)message {
|
||||
[self saveFakeDecision:message];
|
||||
|
||||
char *target = message.path;
|
||||
NSString *target = @(message.path);
|
||||
|
||||
// Check if this file is an executable.
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:@(target)];
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:target];
|
||||
if (fi.isExecutable) {
|
||||
// Check if there is an existing (non-transitive) rule for this file. We leave existing rules
|
||||
// alone, so that a allowlist or blocklist rule can't be overwritten by a transitive one.
|
||||
@@ -81,10 +82,10 @@
|
||||
if (![ruleTable addRules:@[ rule ] cleanSlate:NO error:&err]) {
|
||||
LOGE(@"unable to add new transitive rule to database: %@", err.localizedDescription);
|
||||
} else {
|
||||
[[SNTEventLog logger]
|
||||
writeLog:[NSString
|
||||
stringWithFormat:@"action=ALLOWLIST|pid=%d|pidversion=%d|path=%s|sha256=%@",
|
||||
message.pid, message.pidversion, target, fi.SHA256]];
|
||||
[[SNTEventLog logger] logAllowlist:[[SNTAllowlistInfo alloc] initWithPid:message.pid
|
||||
pidversion:message.pidversion
|
||||
targetPath:target
|
||||
sha256:fi.SHA256]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
@class SNTDriverManager;
|
||||
@class SNTEventLog;
|
||||
@class SNTNotificationQueue;
|
||||
@class SNTSyncdQueue;
|
||||
@@ -26,7 +26,7 @@
|
||||
///
|
||||
@interface SNTDaemonControlController : NSObject <SNTDaemonControlXPC>
|
||||
|
||||
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager
|
||||
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)driverManager
|
||||
notificationQueue:(SNTNotificationQueue *)notQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue;
|
||||
@end
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
#include "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
const static NSString *kBlockBinary = @"BlockBinary";
|
||||
@@ -33,6 +33,7 @@ const static NSString *kAllowTransitive = @"AllowTransitive";
|
||||
const static NSString *kUnknownEventState = @"Unknown";
|
||||
const static NSString *kBlockPrinterWorkaround = @"BlockPrinterWorkaround";
|
||||
const static NSString *kAllowNoFileInfo = @"AllowNoFileInfo";
|
||||
const static NSString *kDenyNoFileInfo = @"DenyNoFileInfo";
|
||||
const static NSString *kAllowNullVNode = @"AllowNullVNode";
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
|
||||
@@ -130,12 +130,19 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
return;
|
||||
}
|
||||
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSError *fileInfoError;
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
|
||||
if (unlikely(!binInfo)) {
|
||||
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
|
||||
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
|
||||
[self.events incrementForFieldValues:@[ (NSString *)kAllowNoFileInfo ]];
|
||||
if (config.failClosed && config.clientMode == SNTClientModeLockdown) {
|
||||
[self.eventProvider postAction:ACTION_RESPOND_DENY forMessage:message];
|
||||
[self.events incrementForFieldValues:@[ (NSString *)kDenyNoFileInfo ]];
|
||||
} else {
|
||||
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
|
||||
[self.events incrementForFieldValues:@[ (NSString *)kAllowNoFileInfo ]];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -224,7 +231,7 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
se.quarantineAgentBundleID = binInfo.quarantineAgentBundleID;
|
||||
|
||||
// Only store events if there is a sync server configured.
|
||||
if ([SNTConfigurator configurator].syncBaseURL) {
|
||||
if (config.syncBaseURL) {
|
||||
dispatch_async(_eventQueue, ^{
|
||||
[self.eventTable addStoredEvent:se];
|
||||
});
|
||||
@@ -234,13 +241,13 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
if (action != ACTION_RESPOND_ALLOW && action != ACTION_RESPOND_ALLOW_COMPILER) {
|
||||
[[SNTEventLog logger] logDeniedExecution:cd withMessage:message];
|
||||
|
||||
if ([[SNTConfigurator configurator] enableBundles] && binInfo.bundle) {
|
||||
if (config.enableBundles && binInfo.bundle) {
|
||||
// If the binary is part of a bundle, find and hash all the related binaries in the bundle.
|
||||
// Let the GUI know hashing is needed. Once the hashing is complete the GUI will send a
|
||||
// message to santad to perform the upload logic for bundles.
|
||||
// See syncBundleEvent:relatedEvents: for more info.
|
||||
se.needsBundleHash = YES;
|
||||
} else if ([[SNTConfigurator configurator] syncBaseURL]) {
|
||||
} else if (config.syncBaseURL) {
|
||||
// So the server has something to show the user straight away, initiate an event
|
||||
// upload for the blocked binary rather than waiting for the next sync.
|
||||
dispatch_async(_eventQueue, ^{
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/santad/DataLayer/SNTEventTable.h"
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
#import "Source/santad/EventProviders/SNTDriverManager.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
@interface SNTExecutionControllerTest : XCTestCase
|
||||
@property id mockConfigurator;
|
||||
@property id mockCodesignChecker;
|
||||
@property id mockDriverManager;
|
||||
@property id mockEventProvider;
|
||||
@property id mockFileInfo;
|
||||
@property id mockRuleDatabase;
|
||||
@property id mockEventDatabase;
|
||||
@@ -38,6 +38,15 @@
|
||||
@property SNTExecutionController *sut;
|
||||
@end
|
||||
|
||||
@interface SNTTestEventProvider : NSObject
|
||||
- (int)postAction:(santa_action_t)action forMessage:(santa_message_t)sm;
|
||||
@end
|
||||
@implementation SNTTestEventProvider
|
||||
- (int)postAction:(santa_action_t)action forMessage:(santa_message_t)sm {
|
||||
return 0;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTExecutionControllerTest
|
||||
|
||||
- (void)setUp {
|
||||
@@ -56,7 +65,7 @@
|
||||
NSURL *url = [NSURL URLWithString:@"https://localhost/test"];
|
||||
OCMStub([self.mockConfigurator syncBaseURL]).andReturn(url);
|
||||
|
||||
self.mockDriverManager = OCMClassMock([SNTDriverManager class]);
|
||||
self.mockEventProvider = OCMClassMock([SNTTestEventProvider class]);
|
||||
|
||||
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
|
||||
OCMStub([self.mockFileInfo alloc]).andReturn(self.mockFileInfo);
|
||||
@@ -68,7 +77,7 @@
|
||||
self.mockRuleDatabase = OCMClassMock([SNTRuleTable class]);
|
||||
self.mockEventDatabase = OCMClassMock([SNTEventTable class]);
|
||||
|
||||
self.sut = [[SNTExecutionController alloc] initWithEventProvider:self.mockDriverManager
|
||||
self.sut = [[SNTExecutionController alloc] initWithEventProvider:self.mockEventProvider
|
||||
ruleTable:self.mockRuleDatabase
|
||||
eventTable:self.mockEventDatabase
|
||||
notifierQueue:nil
|
||||
@@ -95,12 +104,11 @@
|
||||
NSDictionary *eventCounter = [metricSet export][@"metrics"][@"/santa/events"];
|
||||
BOOL foundField;
|
||||
for (NSDictionary *fieldValue in eventCounter[@"fields"][@"action_response"]) {
|
||||
if ([expectedFieldValueName isEqualToString:fieldValue[@"value"]]) {
|
||||
XCTAssertEqualObjects(expectedValue, fieldValue[@"data"],
|
||||
@"%@ counter does not match expected value", expectedFieldValueName);
|
||||
foundField = YES;
|
||||
break;
|
||||
}
|
||||
if (![expectedFieldValueName isEqualToString:fieldValue[@"value"]]) continue;
|
||||
XCTAssertEqualObjects(expectedValue, fieldValue[@"data"],
|
||||
@"%@ counter does not match expected value", expectedFieldValueName);
|
||||
foundField = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!foundField) {
|
||||
@@ -120,7 +128,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:@"AllowBinary" expected:@2];
|
||||
}
|
||||
|
||||
@@ -136,7 +144,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
|
||||
// verify that we're incrementing the binary block
|
||||
[self checkMetricCounters:@"BlockBinary" expected:@1];
|
||||
@@ -157,7 +165,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowCertificate expected:@1];
|
||||
}
|
||||
|
||||
@@ -178,7 +186,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
[self checkMetricCounters:@"BlockCertificate" expected:@1];
|
||||
}
|
||||
@@ -196,7 +204,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW_COMPILER
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW_COMPILER
|
||||
forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowCompiler expected:@1];
|
||||
}
|
||||
@@ -214,7 +222,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowBinary expected:@1];
|
||||
}
|
||||
|
||||
@@ -231,7 +239,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
|
||||
[self checkMetricCounters:@"AllowBinary" expected:@2];
|
||||
}
|
||||
@@ -252,7 +260,7 @@
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
[self checkMetricCounters:kAllowBinary expected:@2];
|
||||
[self checkMetricCounters:kAllowTransitive expected:@1];
|
||||
@@ -266,20 +274,68 @@
|
||||
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
|
||||
[self checkMetricCounters:kBlockUnknown expected:@2];
|
||||
[self checkMetricCounters:kAllowUnknown expected:@1];
|
||||
}
|
||||
|
||||
- (void)testUnreadableFailOpenLockdown {
|
||||
// Undo the default mocks
|
||||
[self.mockFileInfo stopMocking];
|
||||
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
|
||||
|
||||
OCMStub([self.mockFileInfo alloc]).andReturn(nil);
|
||||
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY error:[OCMArg setTo:nil]]).andReturn(nil);
|
||||
|
||||
// Lockdown mode, no fail-closed
|
||||
OCMStub([self.mockConfigurator failClosed]).andReturn(NO);
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowNoFileInfo expected:@2];
|
||||
}
|
||||
|
||||
- (void)testUnreadableFailClosedLockdown {
|
||||
// Undo the default mocks
|
||||
[self.mockFileInfo stopMocking];
|
||||
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
|
||||
|
||||
OCMStub([self.mockFileInfo alloc]).andReturn(nil);
|
||||
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY error:[OCMArg setTo:nil]]).andReturn(nil);
|
||||
|
||||
// Lockdown mode, fail-closed
|
||||
OCMStub([self.mockConfigurator failClosed]).andReturn(YES);
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kDenyNoFileInfo expected:@1];
|
||||
}
|
||||
|
||||
- (void)testUnreadableFailClosedMonitor {
|
||||
// Undo the default mocks
|
||||
[self.mockFileInfo stopMocking];
|
||||
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
|
||||
|
||||
OCMStub([self.mockFileInfo alloc]).andReturn(nil);
|
||||
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY error:[OCMArg setTo:nil]]).andReturn(nil);
|
||||
|
||||
// Monitor mode, fail-closed
|
||||
OCMStub([self.mockConfigurator failClosed]).andReturn(YES);
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowNoFileInfo expected:@1];
|
||||
}
|
||||
|
||||
- (void)testMissingShasum {
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowScope expected:@1];
|
||||
}
|
||||
|
||||
@@ -287,7 +343,7 @@
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(NO);
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowScope expected:@2];
|
||||
}
|
||||
|
||||
@@ -296,7 +352,7 @@
|
||||
OCMStub([self.mockFileInfo isMissingPageZero]).andReturn(YES);
|
||||
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerify([self.mockEventProvider postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
[self checkMetricCounters:kBlockUnknown expected:@3];
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTCommon.h"
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
@class SNTCachedDecision;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/santad/EventProviders/SNTDriverManager.h"
|
||||
#import "Source/santad/SNTApplication.h"
|
||||
|
||||
#include <mach/task.h>
|
||||
@@ -94,8 +93,6 @@ void cleanup() {
|
||||
LOGI(@"com.google.santa.daemon is running from an unexpected path: cleaning up");
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
[fm removeItemAtPath:@"/Library/LaunchDaemons/com.google.santad.plist" error:NULL];
|
||||
[SNTDriverManager unloadDriver];
|
||||
[fm removeItemAtPath:@"/Library/Extensions/santa-driver.kext" error:NULL];
|
||||
|
||||
LOGI(@"loading com.google.santa.daemon as a SystemExtension");
|
||||
NSTask *t = [[NSTask alloc] init];
|
||||
@@ -122,17 +119,19 @@ int main(int argc, const char *argv[]) {
|
||||
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
|
||||
NSProcessInfo *pi = [NSProcessInfo processInfo];
|
||||
|
||||
NSString *productVersion = infoDict[@"CFBundleShortVersionString"];
|
||||
NSString *buildVersion =
|
||||
[[infoDict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
|
||||
|
||||
if ([pi.arguments containsObject:@"-v"]) {
|
||||
printf("%s\n", [infoDict[@"CFBundleVersion"] UTF8String]);
|
||||
printf("%s (build %s)\n", [productVersion UTF8String], [buildVersion UTF8String]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);
|
||||
LOGI(@"Started, version %@ (build %@)", productVersion, buildVersion);
|
||||
|
||||
// Handle the case of macOS < 10.15 updating to >= 10.15.
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
if ([pi.arguments.firstObject isEqualToString:@(kSantaDPath)]) cleanup();
|
||||
}
|
||||
if ([pi.arguments.firstObject isEqualToString:@(kSantaDPath)]) cleanup();
|
||||
|
||||
SNTApplication *s = [[SNTApplication alloc] init];
|
||||
[s start];
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
@@ -39,6 +42,7 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
":SNTMetricWriter",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"@MOLAuthenticatingURLSession",
|
||||
],
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/santametricservice/Writers/SNTMetricHTTPWriter.h"
|
||||
|
||||
@@ -49,37 +50,46 @@
|
||||
dispatch_group_enter(requests);
|
||||
|
||||
request.HTTPBody = (NSData *)value;
|
||||
[[_session dataTaskWithRequest:request
|
||||
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
|
||||
NSError *_Nullable err) {
|
||||
if (err != nil) {
|
||||
_blockError = err;
|
||||
*stop = YES;
|
||||
} else if (response == nil) {
|
||||
*stop = YES;
|
||||
} else if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
NSURLSessionDataTask *task = [_session
|
||||
dataTaskWithRequest:request
|
||||
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
|
||||
NSError *_Nullable err) {
|
||||
if (err != nil) {
|
||||
_blockError = err;
|
||||
*stop = YES;
|
||||
} else if (response == nil) {
|
||||
*stop = YES;
|
||||
} else if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
|
||||
// Check HTTP error codes and create errors for any non-200.
|
||||
if (httpResponse && httpResponse.statusCode != 200) {
|
||||
_blockError = [[NSError alloc]
|
||||
initWithDomain:@"com.google.santa.metricservice.writers.http"
|
||||
code:httpResponse.statusCode
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : [NSString
|
||||
stringWithFormat:@"received http status code %ld from %@",
|
||||
httpResponse.statusCode, url]
|
||||
}];
|
||||
*stop = YES;
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(requests);
|
||||
}] resume];
|
||||
// Check HTTP error codes and create errors for any non-200.
|
||||
if (httpResponse && httpResponse.statusCode != 200) {
|
||||
_blockError = [[NSError alloc]
|
||||
initWithDomain:@"com.google.santa.metricservice.writers.http"
|
||||
code:httpResponse.statusCode
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
[NSString stringWithFormat:@"received http status code %ld from %@",
|
||||
httpResponse.statusCode, url]
|
||||
}];
|
||||
*stop = YES;
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(requests);
|
||||
}];
|
||||
|
||||
// Wait up to 30 seconds for the request to complete.
|
||||
if (dispatch_group_wait(requests, (int64_t)(30.0 * NSEC_PER_SEC)) != 0) {
|
||||
[task resume];
|
||||
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
int64_t timeout = (int64_t)config.metricExportTimeout;
|
||||
|
||||
// Wait up to timeout seconds for the request to complete.
|
||||
if (dispatch_group_wait(requests, dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC))) !=
|
||||
0) {
|
||||
[task cancel];
|
||||
NSString *errMsg =
|
||||
[NSString stringWithFormat:@"HTTP request to %@ timed out after 30 seconds", url];
|
||||
[NSString stringWithFormat:@"HTTP request to %@ timed out after %lu seconds", url,
|
||||
(unsigned long)timeout];
|
||||
|
||||
_blockError = [[NSError alloc] initWithDomain:@"com.google.santa.metricservice.writers.http"
|
||||
code:ETIMEDOUT
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/santametricservice/Writers/SNTMetricHTTPWriter.h"
|
||||
|
||||
@interface SNTMetricHTTPWriterTest : XCTestCase
|
||||
@@ -12,6 +13,7 @@
|
||||
@property id mockMOLAuthenticatingURLSession;
|
||||
@property NSMutableArray<NSDictionary *> *mockResponses;
|
||||
@property SNTMetricHTTPWriter *httpWriter;
|
||||
@property id mockConfigurator;
|
||||
@end
|
||||
|
||||
@implementation SNTMetricHTTPWriterTest
|
||||
@@ -28,6 +30,9 @@
|
||||
self.httpWriter = [[SNTMetricHTTPWriter alloc] init];
|
||||
self.mockResponses = [[NSMutableArray alloc] init];
|
||||
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
|
||||
// This must be marked __unsafe_unretained because we're going to store into
|
||||
// it using NSInvocation's getArgument:atIndex: method which takes a void*
|
||||
// to populate. If we don't mark the variable __unsafe_unretained it will
|
||||
@@ -181,4 +186,18 @@
|
||||
|
||||
XCTAssertFalse(result);
|
||||
}
|
||||
@end
|
||||
|
||||
- (void)testEnsureTimeoutsDoNotCrashWriter {
|
||||
NSURL *url = [NSURL URLWithString:@"http://localhost:11444"];
|
||||
|
||||
// Queue up two responses for nil and NULL.
|
||||
[self createMockResponseWithURL:url withCode:400 withData:nil withError:nil];
|
||||
// Set the timeout to 0 second
|
||||
OCMStub([self.mockConfigurator metricExportTimeout]).andReturn(0);
|
||||
|
||||
NSData *JSONdata = [@"{\"foo\": \"bar\"}\r\n" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
BOOL result = [self.httpWriter write:@[ JSONdata ] toURL:url error:nil];
|
||||
XCTAssertEqual(NO, result);
|
||||
}
|
||||
@end
|
||||
@@ -3,7 +3,10 @@ load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "FCM_lib",
|
||||
|
||||
@@ -21,6 +21,7 @@ extern NSString *const kHostname;
|
||||
extern NSString *const kSantaVer;
|
||||
extern NSString *const kOSVer;
|
||||
extern NSString *const kOSBuild;
|
||||
extern NSString *const kModelIdentifier;
|
||||
extern NSString *const kPrimaryUser;
|
||||
extern NSString *const kRequestCleanSync;
|
||||
extern NSString *const kBatchSize;
|
||||
|
||||
@@ -21,6 +21,7 @@ NSString *const kHostname = @"hostname";
|
||||
NSString *const kSantaVer = @"santa_version";
|
||||
NSString *const kOSVer = @"os_version";
|
||||
NSString *const kOSBuild = @"os_build";
|
||||
NSString *const kModelIdentifier = @"model_identifier";
|
||||
NSString *const kPrimaryUser = @"primary_user";
|
||||
NSString *const kRequestCleanSync = @"request_clean_sync";
|
||||
NSString *const kBatchSize = @"batch_size";
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
@@ -37,6 +37,7 @@
|
||||
requestDict[kHostname] = [SNTSystemInfo longHostname];
|
||||
requestDict[kOSVer] = [SNTSystemInfo osVersion];
|
||||
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
|
||||
requestDict[kModelIdentifier] = [SNTSystemInfo modelIdentifier];
|
||||
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
requestDict[kPrimaryUser] = self.syncState.machineOwner;
|
||||
if (self.syncState.FCMToken) requestDict[kFCMToken] = self.syncState.FCMToken;
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load(
|
||||
"@build_bazel_rules_apple//apple:macos.bzl",
|
||||
"macos_command_line_application",
|
||||
)
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
@@ -20,3 +26,23 @@ test_suite(
|
||||
":SNTExecTest",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "logging_benchmarks_lib",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"SNTLoggingBenchmarks.m",
|
||||
],
|
||||
deps = [
|
||||
"//Source/santad:EndpointSecurityTestLib",
|
||||
"//Source/santad:event_logs",
|
||||
],
|
||||
)
|
||||
|
||||
macos_command_line_application(
|
||||
name = "logging_benchmarks_bin",
|
||||
testonly = 1,
|
||||
bundle_id = "com.goole.santa.benchmark.logging",
|
||||
minimum_os_version = "10.15",
|
||||
deps = [":logging_benchmarks_lib"],
|
||||
)
|
||||
|
||||
159
Testing/integration/SNTLoggingBenchmarks.m
Normal file
159
Testing/integration/SNTLoggingBenchmarks.m
Normal file
@@ -0,0 +1,159 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
// clang-format off
|
||||
/// This benchmarking program pairs well with the hyperfine benchmarking utility:
|
||||
/// https://github.com/sharkdp/hyperfine
|
||||
///
|
||||
/// Some example invocations:
|
||||
/// - Compare performance of different in-memory buffer sizes by controlling file size threshold
|
||||
/// hyperfine --warmup 3 -P fssize 50 500 -D 50 'santa_logging_benchmarks_bin -i 20000 -l protobuf -f {fssize} -s 500 -t 5'
|
||||
///
|
||||
/// - Compare the file logger vs the protobuf logger
|
||||
/// hyperfine --warmup 3 --parameter-list logger file,protobuf 'santa_logging_benchmarks_bin -i 100000 -l {logger} -f 100 -s 500 -t 5'
|
||||
// clang-format on
|
||||
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <getopt.h>
|
||||
#import <stdlib.h>
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/santad/EventProviders/EndpointSecurityTestUtil.h"
|
||||
#import "Source/santad/Logs/SNTFileEventLog.h"
|
||||
#import "Source/santad/Logs/SNTProtobufEventLog.h"
|
||||
#import "Source/santad/Logs/SNTSyslogEventLog.h"
|
||||
|
||||
@interface SNTConfigurator (Testing)
|
||||
@property NSMutableDictionary *configState;
|
||||
@end
|
||||
|
||||
void usage(void) {
|
||||
fprintf(stderr, "Usage: %s [-i <iterations>] [-l (file|syslog|protobuf)]\n", getprogname());
|
||||
}
|
||||
|
||||
void runLogFileModification(santa_message_t *msg, SNTEventLog *eventLog) {
|
||||
msg->action = ACTION_NOTIFY_RENAME;
|
||||
[eventLog logFileModification:*msg];
|
||||
}
|
||||
|
||||
BOOL createTestDir(NSURL *dir) {
|
||||
return [[NSFileManager defaultManager] createDirectoryAtURL:dir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil];
|
||||
}
|
||||
|
||||
void setup(int iterations, SNTEventLog *eventLog) {
|
||||
// Create and populate necessary values and data structures in advance to
|
||||
// minimze the effect on overall run time.
|
||||
static const char *commonProcName = "launchd";
|
||||
static const char *commonPath = "/sbin/launchd";
|
||||
static const char *commonNewPath = "/foo/bar.txt";
|
||||
NSArray *execArgs = @[ @"/sbin/launchd", @"--init", @"--testing" ];
|
||||
struct timespec ts = {123, 456};
|
||||
|
||||
es_file_t esFile = MakeESFile(commonPath);
|
||||
es_process_t esProc = MakeESProcess(&esFile);
|
||||
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_RENAME, &esProc, ts);
|
||||
|
||||
santa_message_t santaMsg = {0};
|
||||
|
||||
santaMsg.uid = 242;
|
||||
santaMsg.gid = 20;
|
||||
santaMsg.pid = 1;
|
||||
santaMsg.pidversion = 2;
|
||||
santaMsg.ppid = 3;
|
||||
|
||||
strlcpy(santaMsg.path, commonPath, sizeof(santaMsg.path));
|
||||
strlcpy(santaMsg.newpath, commonNewPath, sizeof(santaMsg.newpath));
|
||||
strlcpy(santaMsg.pname, commonProcName, sizeof(santaMsg.pname));
|
||||
|
||||
santaMsg.args_array = (__bridge void *)execArgs;
|
||||
santaMsg.es_message = &esMsg;
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
[eventLog logFileModification:santaMsg];
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
static const struct option longopts[] = {
|
||||
{"iter", required_argument, NULL, 'i'},
|
||||
{"logger", required_argument, NULL, 'l'},
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
int ch;
|
||||
int iterations = 5000;
|
||||
Class eventLogClass = [SNTProtobufEventLog class];
|
||||
SNTConfigurator *configurator = [SNTConfigurator configurator];
|
||||
unsigned int fileSize = 100;
|
||||
unsigned int dirSize = 500;
|
||||
float flushFrequency = 5.0;
|
||||
|
||||
while ((ch = getopt_long(argc, argv, "f:i:l:s:t:", longopts, NULL)) != -1) {
|
||||
switch (ch) {
|
||||
case 'f': fileSize = atoi(optarg); break;
|
||||
case 'i': iterations = atoi(optarg); break;
|
||||
case 'l':
|
||||
if (strcmp(optarg, "syslog") == 0) {
|
||||
eventLogClass = [SNTSyslogEventLog class];
|
||||
} else if (strcmp(optarg, "file") == 0) {
|
||||
eventLogClass = [SNTFileEventLog class];
|
||||
} else if (strcmp(optarg, "protobuf") == 0) {
|
||||
eventLogClass = [SNTProtobufEventLog class];
|
||||
} else {
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 's': dirSize = atoi(optarg); break;
|
||||
case 't': flushFrequency = (float)atoi(optarg); break;
|
||||
default: usage(); exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
NSURL *santaTestDir =
|
||||
[NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"santa_test"]];
|
||||
NSURL *tempDir = [santaTestDir URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
|
||||
NSURL *mailDir = [tempDir URLByAppendingPathComponent:@"pblogger"];
|
||||
NSURL *fileDir = [tempDir URLByAppendingPathComponent:@"filelogger"];
|
||||
NSString *eventLogPath = [[fileDir URLByAppendingPathComponent:@"santa.log"] path];
|
||||
|
||||
[[NSFileManager defaultManager] createDirectoryAtURL:mailDir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil];
|
||||
[[NSFileManager defaultManager] createDirectoryAtURL:fileDir
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil];
|
||||
|
||||
configurator.configState[@"EventLogPath"] = eventLogPath;
|
||||
configurator.configState[@"MailDirectory"] = mailDir.path;
|
||||
configurator.configState[@"MailDirectoryFileSizeThresholdKB"] = @(fileSize);
|
||||
configurator.configState[@"MailDirectorySizeThresholdMB"] = @(dirSize);
|
||||
configurator.configState[@"MailDirectoryEventMaxFlushTimeSec"] = @(flushFrequency);
|
||||
|
||||
NSLog(@"Using log path: %@", configurator.eventLogPath);
|
||||
NSLog(@"Using mail dir: %@", configurator.mailDirectory);
|
||||
NSLog(@"Using logger: %@", eventLogClass);
|
||||
|
||||
SNTEventLog *eventLog = [[eventLogClass alloc] init];
|
||||
setup(iterations, eventLog);
|
||||
[eventLog forceFlush];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user