mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16f74cb85c | ||
|
|
aadc961429 | ||
|
|
be66fd92f4 | ||
|
|
feea349f25 | ||
|
|
1c04c3a257 | ||
|
|
818d3f645f | ||
|
|
15d6bb1f14 | ||
|
|
211dbd123f | ||
|
|
c67364fe76 | ||
|
|
2043983f69 | ||
|
|
2f408936a0 | ||
|
|
02c1d0f267 | ||
|
|
4728c346cc | ||
|
|
9588dd8a0e |
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
|
||||
|
||||
53
BUILD
53
BUILD
@@ -54,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
|
||||
""",
|
||||
)
|
||||
@@ -73,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"
|
||||
@@ -183,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 = [
|
||||
|
||||
@@ -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,7 +44,6 @@ 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"
|
||||
@@ -54,19 +53,18 @@ readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Inf
|
||||
|
||||
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(
|
||||
@@ -57,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"],
|
||||
@@ -95,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(
|
||||
@@ -129,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"],
|
||||
@@ -169,6 +167,7 @@ objc_library(
|
||||
cc_library(
|
||||
name = "SNTStrengthify",
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
features = ["layering_check"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
@@ -251,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
|
||||
@@ -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";
|
||||
|
||||
@@ -153,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.
|
||||
///
|
||||
@@ -168,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.
|
||||
@@ -175,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
|
||||
|
||||
@@ -80,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";
|
||||
@@ -186,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,
|
||||
@@ -364,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];
|
||||
}
|
||||
@@ -372,10 +394,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -670,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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -228,19 +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);
|
||||
if (cachingEnabled) {
|
||||
printf(">>> Cache Info\n");
|
||||
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
|
||||
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
|
||||
}
|
||||
|
||||
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(">>> 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,26 +67,6 @@ 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"];
|
||||
|
||||
@@ -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,27 +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",
|
||||
],
|
||||
)
|
||||
@@ -159,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",
|
||||
@@ -231,7 +343,7 @@ santa_unit_test(
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityTestLib",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
],
|
||||
@@ -251,7 +363,7 @@ santa_unit_test(
|
||||
":DiskArbitrationTestLib",
|
||||
":EndpointSecurityTestLib",
|
||||
":santad_lib",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTCommon",
|
||||
"//Source/common:SNTPrefixTree",
|
||||
"//Source/common:SantaCache",
|
||||
"@OCMock",
|
||||
@@ -318,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
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
__block NSString *gotmntonname, *gotmntfromname;
|
||||
__block NSArray<NSString *> *gotRemountedArgs;
|
||||
deviceManager.deviceBlockCallbacks = ^(SNTDeviceEvent *event) {
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
gotRemountedArgs = event.remountArgs;
|
||||
gotmntonname = event.mntonname;
|
||||
gotmntfromname = event.mntfromname;
|
||||
@@ -172,7 +172,7 @@
|
||||
|
||||
__block NSString *gotmntonname, *gotmntfromname;
|
||||
__block NSArray<NSString *> *gotRemountedArgs;
|
||||
deviceManager.deviceBlockCallbacks = ^(SNTDeviceEvent *event) {
|
||||
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
|
||||
gotRemountedArgs = event.remountArgs;
|
||||
gotmntonname = event.mntonname;
|
||||
gotmntfromname = event.mntfromname;
|
||||
|
||||
@@ -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,7 +14,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTCommon.h"
|
||||
#include "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
@@ -179,6 +179,14 @@ static const pid_t PID_MAX = 99999;
|
||||
});
|
||||
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;
|
||||
@@ -421,6 +429,9 @@ static const pid_t PID_MAX = 99999;
|
||||
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)) {
|
||||
@@ -437,6 +448,10 @@ static const pid_t PID_MAX = 99999;
|
||||
// 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);
|
||||
@@ -444,7 +459,7 @@ static const pid_t PID_MAX = 99999;
|
||||
// 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)) {
|
||||
|
||||
@@ -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,6 +17,7 @@
|
||||
#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"
|
||||
@@ -192,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];
|
||||
@@ -288,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
|
||||
|
||||
@@ -31,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"
|
||||
@@ -61,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) {
|
||||
@@ -164,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
|
||||
@@ -379,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";
|
||||
|
||||
@@ -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
|
||||
@@ -119,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];
|
||||
}
|
||||
|
||||
@@ -135,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];
|
||||
@@ -156,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];
|
||||
}
|
||||
|
||||
@@ -177,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];
|
||||
}
|
||||
@@ -195,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];
|
||||
}
|
||||
@@ -213,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];
|
||||
}
|
||||
|
||||
@@ -230,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];
|
||||
}
|
||||
@@ -251,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];
|
||||
@@ -265,11 +274,11 @@
|
||||
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];
|
||||
@@ -288,7 +297,7 @@
|
||||
OCMStub([self.mockConfigurator failClosed]).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:kAllowNoFileInfo expected:@2];
|
||||
}
|
||||
|
||||
@@ -304,7 +313,7 @@
|
||||
OCMStub([self.mockConfigurator failClosed]).andReturn(YES);
|
||||
OCMStub([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]]);
|
||||
[self checkMetricCounters:kDenyNoFileInfo expected:@1];
|
||||
}
|
||||
|
||||
@@ -320,13 +329,13 @@
|
||||
OCMStub([self.mockConfigurator failClosed]).andReturn(YES);
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
|
||||
[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: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];
|
||||
}
|
||||
|
||||
@@ -334,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];
|
||||
}
|
||||
|
||||
@@ -343,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];
|
||||
@@ -134,9 +131,7 @@ int main(int argc, const char *argv[]) {
|
||||
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"])
|
||||
|
||||
|
||||
@@ -84,7 +84,8 @@
|
||||
int64_t timeout = (int64_t)config.metricExportTimeout;
|
||||
|
||||
// Wait up to timeout seconds for the request to complete.
|
||||
if (dispatch_group_wait(requests, (timeout * NSEC_PER_SEC)) != 0) {
|
||||
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 %lu seconds", url,
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
25
WORKSPACE
25
WORKSPACE
@@ -12,6 +12,31 @@ git_repository(
|
||||
remote = "https://github.com/bazelbuild/rules_apple.git",
|
||||
)
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "rules_proto_grpc",
|
||||
sha256 = "28724736b7ff49a48cb4b2b8cfa373f89edfcb9e8e492a8d5ab60aa3459314c8",
|
||||
strip_prefix = "rules_proto_grpc-4.0.1",
|
||||
urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.0.1.tar.gz"],
|
||||
)
|
||||
|
||||
load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains")
|
||||
|
||||
rules_proto_grpc_toolchains()
|
||||
|
||||
rules_proto_grpc_repos()
|
||||
|
||||
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
|
||||
|
||||
rules_proto_dependencies()
|
||||
|
||||
rules_proto_toolchains()
|
||||
|
||||
load("@rules_proto_grpc//objc:repositories.bzl", rules_proto_grpc_objc_repos = "objc_repos")
|
||||
|
||||
rules_proto_grpc_objc_repos()
|
||||
|
||||
load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies")
|
||||
|
||||
apple_rules_dependencies()
|
||||
|
||||
@@ -21,44 +21,48 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
|
||||
|
||||
| Key | Value Type | Description |
|
||||
| ----------------------------- | ---------- | ---------------------------------------- |
|
||||
| ClientMode\* | Integer | 1 = MONITOR, 2 = LOCKDOWN, defaults to MONITOR |
|
||||
| FailClosed | Bool | If true and the ClientMode is LOCKDOWN: execution will be denied when there is an error reading or processing an executable file. |
|
||||
| FileChangesRegex\* | String | The regex of paths to log file changes. Regexes are specified in ICU format. |
|
||||
| AllowedPathRegex\* | String | A regex to allow if the binary or certificate scopes did not allow/block execution. Regexes are specified in ICU format. |
|
||||
| BlockedPathRegex\* | String | A regex to block if the binary or certificate scopes did not allow/block an execution. Regexes are specified in ICU format. |
|
||||
| EnableBadSignatureProtection | Bool | Enable bad signature protection, defaults to NO. If this flag is set to YES, binaries with a bad signing chain will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
|
||||
| EnablePageZeroProtection | Bool | Enable `__PAGEZERO` protection, defaults to YES. If this flag is set to YES, 32-bit binaries that are missing the `__PAGEZERO` segment will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
|
||||
| EnableSysxCache | Bool | Enables a secondary cache that ensures better performance when multiple EndpointSecurity system extensions are installed. Defaults to YES in 2021.8, defaults to NO in earlier versions. |
|
||||
| AboutText | String | The text to display when the user opens Santa.app. If unset, the default text will be displayed. |
|
||||
| MoreInfoURL | String | The URL to open when the user clicks "More Info..." when opening Santa.app. If unset, the button will not be displayed. |
|
||||
| EventDetailURL | String | See the [EventDetailURL](#eventdetailurl) section below. |
|
||||
| EventDetailText | String | Related to the above property, this string represents the text to show on the button. |
|
||||
| UnknownBlockMessage | String | In Lockdown mode this is the message shown to the user when an unknown binary is blocked. If this message is not configured a reasonable default is provided. |
|
||||
| BannedBlockMessage | String | This is the message shown to the user when a binary is blocked because of a rule if that rule doesn't provide a custom message. If this is not configured a reasonable default is provided. |
|
||||
| ModeNotificationMonitor | String | The notification text to display when the client goes into Monitor mode. Defaults to "Switching into Monitor mode". |
|
||||
| ModeNotificationLockdown | String | The notification text to display when the client goes into Lockdown mode. Defaults to "Switching into Lockdown mode". |
|
||||
| SyncBaseURL | String | The base URL of the sync server. |
|
||||
| SyncProxyConfiguration | Dictionary | The proxy configuration to use when syncing. See the [Apple Documentation](https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants) for details on the keys that can be used in this dictionary. |
|
||||
| ClientAuthCertificateFile | String | If set, this contains the location of a PKCS#12 certificate to be used for sync authentication. |
|
||||
| ClientAuthCertificatePassword | String | Contains the password for the PKCS#12 certificate. |
|
||||
| ClientAuthCertificateCN | String | If set, this is the Common Name of a certificate in the System keychain to be used for sync authentication. The corresponding private key must also be in the keychain. |
|
||||
| ClientAuthCertificateIssuerCN | String | If set, this is the Issuer Name of a certificate in the System keychain to be used for sync authentication. The corresponding private key must also be in the keychain. |
|
||||
| ServerAuthRootsData | Data | If set, this is valid PEM containing one or more certificates to be used for certificate pinning. To comply with [ATS](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW57) the certificate chain must also be trusted in the keychain. |
|
||||
| ServerAuthRootsFile | String | The same as the above but is a path to a file on disk containing the PEM data. |
|
||||
| MachineOwner | String | The machine owner. |
|
||||
| MachineID | String | The machine ID. |
|
||||
| MachineOwnerPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
|
||||
| MachineOwnerKey | String | The key to use on MachineOwnerPlist. |
|
||||
| MachineIDPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
|
||||
| MachineIDKey | String | The key to use on MachineIDPlist. |
|
||||
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. Defaults to filelog |
|
||||
| EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. |
|
||||
| EnableMachineIDDecoration | Bool | If YES, this appends the MachineID to the end of each log line. Defaults to NO. |
|
||||
| MetricFormat | String | Format to export metrics as, supported formats are "rawjson" for a single JSON blob and "monarchjson" for a format consumable by Google's Monarch tooling. Defaults to "". |
|
||||
| MetricURL | String | URL describing where monitoring metrics should be exported. |
|
||||
| MetricExportInterval | Integer | Number of seconds to wait between exporting metrics. Defaults to 30. |
|
||||
| MetricExportTimeout | Integer | Number of seconds to wait before a timeout occurs when exporting metrics. Defaults to 30. |
|
||||
| MetricExtraLabels | Dictionary | A map of key value pairs to add to all metric root labels. (e.g. a=b,c=d) defaults to @{}). If a previously set key (e.g. host_name is set to "" then the key is remove from the metric root labels. Alternatively if a value is set for an existing key then the new value will override the old. |
|
||||
| ClientMode\* | Integer | 1 = MONITOR, 2 = LOCKDOWN, defaults to MONITOR |
|
||||
| FailClosed | Bool | If true and the ClientMode is LOCKDOWN: execution will be denied when there is an error reading or processing an executable file. |
|
||||
| FileChangesRegex\* | String | The regex of paths to log file changes. Regexes are specified in ICU format. |
|
||||
| AllowedPathRegex\* | String | A regex to allow if the binary or certificate scopes did not allow/block execution. Regexes are specified in ICU format. |
|
||||
| BlockedPathRegex\* | String | A regex to block if the binary or certificate scopes did not allow/block an execution. Regexes are specified in ICU format. |
|
||||
| EnableBadSignatureProtection | Bool | Enable bad signature protection, defaults to NO. If this flag is set to YES, binaries with a bad signing chain will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
|
||||
| EnablePageZeroProtection | Bool | Enable `__PAGEZERO` protection, defaults to YES. If this flag is set to YES, 32-bit binaries that are missing the `__PAGEZERO` segment will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
|
||||
| EnableSysxCache | Bool | Enables a secondary cache that ensures better performance when multiple EndpointSecurity system extensions are installed. Defaults to YES in 2021.8, defaults to NO in earlier versions. |
|
||||
| AboutText | String | The text to display when the user opens Santa.app. If unset, the default text will be displayed. |
|
||||
| MoreInfoURL | String | The URL to open when the user clicks "More Info..." when opening Santa.app. If unset, the button will not be displayed. |
|
||||
| EventDetailURL | String | See the [EventDetailURL](#eventdetailurl) section below. |
|
||||
| EventDetailText | String | Related to the above property, this string represents the text to show on the button. |
|
||||
| UnknownBlockMessage | String | In Lockdown mode this is the message shown to the user when an unknown binary is blocked. If this message is not configured a reasonable default is provided. |
|
||||
| BannedBlockMessage | String | This is the message shown to the user when a binary is blocked because of a rule if that rule doesn't provide a custom message. If this is not configured a reasonable default is provided. |
|
||||
| ModeNotificationMonitor | String | The notification text to display when the client goes into Monitor mode. Defaults to "Switching into Monitor mode". |
|
||||
| ModeNotificationLockdown | String | The notification text to display when the client goes into Lockdown mode. Defaults to "Switching into Lockdown mode". |
|
||||
| SyncBaseURL | String | The base URL of the sync server. |
|
||||
| SyncProxyConfiguration | Dictionary | The proxy configuration to use when syncing. See the [Apple Documentation](https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants) for details on the keys that can be used in this dictionary. |
|
||||
| ClientAuthCertificateFile | String | If set, this contains the location of a PKCS#12 certificate to be used for sync authentication. |
|
||||
| ClientAuthCertificatePassword | String | Contains the password for the PKCS#12 certificate. |
|
||||
| ClientAuthCertificateCN | String | If set, this is the Common Name of a certificate in the System keychain to be used for sync authentication. The corresponding private key must also be in the keychain. |
|
||||
| ClientAuthCertificateIssuerCN | String | If set, this is the Issuer Name of a certificate in the System keychain to be used for sync authentication. The corresponding private key must also be in the keychain. |
|
||||
| ServerAuthRootsData | Data | If set, this is valid PEM containing one or more certificates to be used for certificate pinning. To comply with [ATS](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW57) the certificate chain must also be trusted in the keychain. |
|
||||
| ServerAuthRootsFile | String | The same as the above but is a path to a file on disk containing the PEM data. |
|
||||
| MachineOwner | String | The machine owner. |
|
||||
| MachineID | String | The machine ID. |
|
||||
| MachineOwnerPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
|
||||
| MachineOwnerKey | String | The key to use on MachineOwnerPlist. |
|
||||
| MachineIDPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
|
||||
| MachineIDKey | String | The key to use on MachineIDPlist. |
|
||||
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. 3) protobuf (BETA): Sent to file on disk using maildir format. Defaults to filelog. |
|
||||
| EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. |
|
||||
| MailDirectory | String | If EventLogType is set to protobuf, MailDirectory will provide the the base directory used to save files according to the maildir format. Defaults to /var/db/santa/mail. |
|
||||
| MailDirectoryFileSizeThresholdKB | Integer | If EventLogType is set to protobuf, MailDirectoryFileSizeThresholdKB defines the per-file size limit for files stored in the mail directory. Events are buffered in memory until this threshold would be exceeded (or MailDirectoryEventMaxFlushTimeSec is exceeded). Defaults to 100. |
|
||||
| MailDirectorySizeThresholdMB | Integer | If EventLogType is set to protobuf, MailDirectorySizeThresholdMB defines the total combined size limit of all files in the mail directory. Once the threshold is met, no more events will be saved. Defaults to 500. |
|
||||
| MailDirectoryEventMaxFlushTimeSec | Integer | If EventLogType is set to protobuf, MailDirectoryEventMaxFlushTimeSec defines the maximum amount of time events will stay buffered in memory before being flushed to disk, regardless of whether or not MailDirectoryFileSizeThresholdKB would be exceeded. Defaults to 5. |
|
||||
| EnableMachineIDDecoration | Bool | If YES, this appends the MachineID to the end of each log line. Defaults to NO. |
|
||||
| MetricFormat | String | Format to export metrics as, supported formats are "rawjson" for a single JSON blob and "monarchjson" for a format consumable by Google's Monarch tooling. Defaults to "". |
|
||||
| MetricURL | String | URL describing where monitoring metrics should be exported. |
|
||||
| MetricExportInterval | Integer | Number of seconds to wait between exporting metrics. Defaults to 30. |
|
||||
| MetricExportTimeout | Integer | Number of seconds to wait before a timeout occurs when exporting metrics. Defaults to 30. |
|
||||
| MetricExtraLabels | Dictionary | A map of key value pairs to add to all metric root labels. (e.g. a=b,c=d) defaults to @{}). If a previously set key (e.g. host_name is set to "" then the key is remove from the metric root labels. Alternatively if a value is set for an existing key then the new value will override the old. |
|
||||
|
||||
|
||||
*overridable by the sync server: run `santactl status` to check the current
|
||||
|
||||
@@ -10,10 +10,6 @@ Most IPC within Santa is done by way of Apple's
|
||||
to provide client multiplexing, signature validation of connecting clients and
|
||||
forced connection establishment. This is called SNTXPCConnection.
|
||||
|
||||
Communication between santad and santa-driver (KEXT) is done with a
|
||||
[IOUserClient](https://developer.apple.com/documentation/kernel/iouserclient?language=objc)
|
||||
subclass and IOKit/IOKitLib.h functions.
|
||||
|
||||
##### Who starts who?
|
||||
|
||||
The santad and Santa (GUI) processes are both started and kept alive by launchd
|
||||
|
||||
@@ -30,9 +30,3 @@ flight, including messages related to the system extension:
|
||||
```sh
|
||||
/usr/bin/log show --info --debug --predicate 'senderImagePath CONTAINS[c] "santa"'
|
||||
```
|
||||
|
||||
For those still using the kernel extension, you could use a more specific command:
|
||||
|
||||
```sh
|
||||
/usr/bin/log show --info --debug --predicate 'senderImagePath == "/Library/Extensions/santa-driver.kext/Contents/MacOS/santa-driver"'
|
||||
````
|
||||
@@ -1,139 +0,0 @@
|
||||
---
|
||||
parent: Details
|
||||
---
|
||||
|
||||
# santa-driver
|
||||
|
||||
santa-driver is a macOS
|
||||
[kernel extension](https://developer.apple.com/library/content/documentation/Darwin/Conceptual/KEXTConcept/KEXTConceptIntro/introduction.html)
|
||||
(KEXT) that makes use of the
|
||||
[Kernel Authorization](https://developer.apple.com/library/content/technotes/tn2127/_index.html)
|
||||
(Kauth) KPI. This allows santa-driver to listen for events and either deny or
|
||||
defer the decision of those events. The santa-driver acts as an intermediary
|
||||
layer between Kauth and santad, with some caching to lower the overhead of
|
||||
decision making.
|
||||
|
||||
##### Kauth
|
||||
|
||||
santa-driver utilizes two Kauth scopes `KAUTH_SCOPE_VNODE` and
|
||||
`KAUTH_SCOPE_FILEOP`. It registers itself with the Kauth API by calling
|
||||
`kauth_listen_scope()` for each scope. This function takes three arguments:
|
||||
|
||||
* `const char *scope`
|
||||
* `kauth_scope_callback_t _callback`
|
||||
* `void *context`
|
||||
|
||||
It returns a `kauth_listener_t` that is stored for later use, in Santa's case to
|
||||
stop listening.
|
||||
|
||||
###### KAUTH_SCOPE_VNODE
|
||||
|
||||
Here is how santa-driver starts listening for `KAUTH_SCOPE_VNODE` events.
|
||||
|
||||
```c++
|
||||
vnode_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
|
||||
```
|
||||
|
||||
The function `vnode_scope_callback` is called for every vnode event. There are
|
||||
many types of vnode events, they complete list can be viewed in the kauth.h.
|
||||
There are many types of vnode events, the complete list can be viewed in
|
||||
kauth.h. Santa is only concerned with regular files generating
|
||||
`KAUTH_VNODE_EXECUTE` [1] and `KAUTH_VNODE_WRITE_DATA` events. All non-regular
|
||||
files and unnecessary vnode events are filtered out.
|
||||
|
||||
Here is how santa-driver stops listening for `KAUTH_SCOPE_VNODE` events:
|
||||
|
||||
```c++
|
||||
kauth_unlisten_scope(vnode_listener_);
|
||||
```
|
||||
|
||||
[1] `KAUTH_VNODE_EXECUTE` events that do not have the `KAUTH_VNODE_ACCESS`
|
||||
advisory bit set.
|
||||
|
||||
###### KAUTH_SCOPE_FILEOP
|
||||
|
||||
Santa also listens for file operations, this is mainly used for logging [1] and
|
||||
cache invalidation.
|
||||
|
||||
* `KAUTH_FILEOP_DELETE`, `KAUTH_FILEOP_RENAME`, `KAUTH_FILEOP_EXCHANGE` and
|
||||
`KAUTH_FILEOP_LINK` are logged
|
||||
* `KAUTH_FILEOP_EXEC` is used to log `execve()`s. Since the
|
||||
`KAUTH_VNODE_EXECUTE` is used to allow or deny an `execve()` the process
|
||||
arguments have not been setup yet. Since `KAUTH_FILEOP_EXEC` is triggered
|
||||
after an `execve()` it is used to log the `execve()`.
|
||||
|
||||
[1] `KAUTH_FILEOP_CLOSE` is used to invalidate that file's representation in the
|
||||
cache. If a file has changed it needs to be re-evalauted. This is particularly
|
||||
necessary for files that were added to the cache with an action of allow.
|
||||
|
||||
##### Driver Interface
|
||||
|
||||
santa-driver implements an
|
||||
[IOUserClient](https://developer.apple.com/documentation/kernel/iouserclient?language=objc)
|
||||
subclass and santad interacts with it through IOKit/IOKitLib.h functions.
|
||||
|
||||
[//]: # "TODO(bur, rah) Flesh out the details"
|
||||
|
||||
##### Cache
|
||||
|
||||
To aid in performance, santa-driver utilizes a caching system to hold the state
|
||||
of all observed `execve()` events.
|
||||
|
||||
###### Key
|
||||
|
||||
The key is a `uint64_t`. The top 32 bits hold the filesystem ID, while the
|
||||
bottom 32 bits hold the file unique ID. Together we call this the vnode_id.
|
||||
|
||||
```c++
|
||||
uint64_t vnode_id = (((uint64_t)fsid << 32) | fileid);
|
||||
```
|
||||
|
||||
###### Value
|
||||
|
||||
The value is a `uint64_t` containing the action necessary, along with the
|
||||
decision timestamp. The action is stored in the top 8 bits. The decision
|
||||
timestamp is stored in the remaining 56 bits.
|
||||
|
||||
```c++
|
||||
santa_action_t action = (santa_action_t)(cache_val >> 56);
|
||||
uint64_t decision_time = (cache_val & ~(0xFF00000000000000));
|
||||
```
|
||||
|
||||
The possible actions are:
|
||||
|
||||
| Actions | Expiry Time | Description |
|
||||
| ----------------------- | ---------------- | ------------------------------ |
|
||||
| `ACTION_REQUEST_BINARY` | None | Awaiting an allow or deny |
|
||||
| | | decision from santad. |
|
||||
| `ACTION_RESPOND_ALLOW` | None | Allow the `execve()` |
|
||||
| `ACTION_RESPOND_DENY` | 500 milliseconds | Deny the `execve()`, but |
|
||||
| | | re-evalaute after 500 |
|
||||
| | | milliseconds. If someone is |
|
||||
| | | trying to run a banned binary |
|
||||
| | | continually every millisecond |
|
||||
| | | for example, only 2 evaluation |
|
||||
| | | requests to santad for would |
|
||||
| | | occur per second. This |
|
||||
| | | mitigates a denial of service |
|
||||
| | | type attack on santad. |
|
||||
|
||||
###### Invalidation
|
||||
|
||||
Besides the expiry time for individual entries, the entire cache will be cleared
|
||||
if any of the following events takes place:
|
||||
|
||||
* Addition of a block rule
|
||||
* Change to the blocked path regex
|
||||
* Cache fills up. This defaults to `5000` entries for the root volume and
|
||||
`500` for all other mounted volumes.
|
||||
|
||||
To view the current kernel cache count see the "Kernel info" section of
|
||||
`santactl status`:
|
||||
|
||||
```sh
|
||||
⇒ santactl status
|
||||
>>> Kernel Info
|
||||
Root cache count | 107
|
||||
Non-root cache count | 0
|
||||
```
|
||||
@@ -4,7 +4,7 @@ parent: Details
|
||||
|
||||
# santabs
|
||||
|
||||
The santabs process is an XPC service for the santa-driver.kext bundle, meaning
|
||||
The santabs process is an XPC service for the santad bundle, meaning
|
||||
only binaries within that bundle can launch santabs. It will be launched with
|
||||
the same privileges as its calling process. Currently, santad is the only caller
|
||||
of santabs, so santabs runs as root.
|
||||
|
||||
@@ -80,7 +80,6 @@ To view all of the component versions `santactl version`
|
||||
|
||||
```sh
|
||||
⇒ santactl version
|
||||
santa-driver | 0.9.19
|
||||
santad | 0.9.19
|
||||
santactl | 0.9.19
|
||||
SantaGUI | 0.9.19
|
||||
@@ -91,7 +90,6 @@ Again, a JSON version is available `santactl version --json`
|
||||
```sh
|
||||
⇒ santactl version --json
|
||||
{
|
||||
"santa-driver" : "0.9.19",
|
||||
"santad" : "0.9.19",
|
||||
"SantaGUI" : "0.9.19",
|
||||
"santactl" : "0.9.19"
|
||||
@@ -408,35 +406,3 @@ BundleID: com.ridiculousfish.HexFiend
|
||||
|
||||
See the [santabs.md](santabs.md) document for more information on bundles and
|
||||
bundle hashes.
|
||||
|
||||
##### checkcache
|
||||
|
||||
This is used to check if a particular file is apart of santa-driver's kernel
|
||||
cache. Mainly for debugging purposes.
|
||||
|
||||
```sh
|
||||
⇒ santactl checkcache /usr/bin/yes
|
||||
File does not exist in cache
|
||||
⇒ /usr/bin/yes
|
||||
y
|
||||
y
|
||||
y
|
||||
y
|
||||
y
|
||||
^C
|
||||
⇒ santactl checkcache /usr/bin/yes
|
||||
File exists in [allowlist] kernel cache
|
||||
```
|
||||
|
||||
##### flushcache
|
||||
|
||||
This can be used to flush santa-driver's kernel cache, as shown here.
|
||||
|
||||
```sh
|
||||
⇒ santactl checkcache /usr/bin/yes
|
||||
File exists in [allowlist] kernel cache
|
||||
⇒ sudo santactl flushcache
|
||||
Cache flush requested
|
||||
⇒ santactl checkcache /usr/bin/yes
|
||||
File does not exist in cache
|
||||
```
|
||||
|
||||
@@ -35,8 +35,7 @@ For those who want even more details on how Santa works under the hood, this sec
|
||||
|
||||
There are five main components that make up Santa whose core functionality is described in snippets below. For additional detail on each component, visit their respective pages. These quick descriptions do not encompass all the jobs performed by each component, but do provide a quick look at the basic functionality utilized to achieve the goal of binary authorization.
|
||||
|
||||
* [santa-driver](details/santa-driver.md): A macOS kernel extension that participates in `execve()` decisions.
|
||||
* [santad](details/santad.md): A user-land root daemon that makes decisions on behalf of santa-driver requests.
|
||||
* [santad](details/santad.md): A user-land root daemon that makes decisions.
|
||||
* [santactl](details/santactl.md): A user-land anonymous daemon that communicates with a sync server for configurations and policies. santactl can also be used by a user to manually configure Santa when using the local configuration.
|
||||
* [santa-gui](details/santa-gui.md): A user-land GUI daemon that displays notifications when an `execve()` is blocked.
|
||||
* [santabs](details/santabs.md): A user-land root daemon that finds Mach-O binaries within a bundle and creates events for them.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""The version for all Santa components."""
|
||||
|
||||
SANTA_VERSION = "2022.2"
|
||||
SANTA_VERSION = "2022.3"
|
||||
|
||||
Reference in New Issue
Block a user