Compare commits

...

14 Commits

Author SHA1 Message Date
Kent Ma
16f74cb85c Remove references to santa-driver and the kernel extension from parts of the docs (#762) 2022-03-21 11:33:45 -04:00
Russell Hancox
aadc961429 santad: Clear caches when disks are unmounted. (#760)
This restores behavior that was recently removed
2022-03-18 13:38:35 -04:00
Russell Hancox
be66fd92f4 santactl/status: Re-org output in status re: USB Blocking. (#759) 2022-03-18 09:57:34 -04:00
Russell Hancox
feea349f25 Project: Remove kext signing/packaging (#755) 2022-03-16 17:08:59 -04:00
Kent Ma
1c04c3a257 Remove code guarded by #ifdef kernel macros (#752)
* Remove code guarded by #ifdef kernel macros
2022-03-15 14:38:40 -04:00
np5
818d3f645f santactl/sync: Add model identifier to preflight request (#751) 2022-03-15 14:24:05 +00:00
Pete Markowsky
15d6bb1f14 Made santad an early boot client to prevent racing other pids. (#750)
Make santad an early boot Endpoint Security Framework Client.
2022-03-15 10:16:40 -04:00
Kent Ma
211dbd123f Remove the Santa kernel extension. (#749)
This includes:

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

Fix build issues in the integration test

Deduped some test code

Formatting

Address feedback from draft PR

Removed legacy labels. Updated docs.

Add in metrics. Fix protobuf logging test.

* Now use the Any proto for the LogBatch wrapper

* Changes based on PR feedback

* Added gauge metrics for spool dir

* Formatting

* Add event time to proto

* Fix build issue after rebase

* Update BUILD rules

* Updated language around protobuf logging to mark as beta
2022-03-14 15:46:52 -04:00
Pete Markowsky
2043983f69 Fix typo in SNTDeviceManager tests & ensure they run in the CI. (#746) 2022-03-14 12:57:07 -04:00
Russell Hancox
2f408936a0 Project: Disable bazel layering_check feature for most rules (#742) 2022-03-10 10:07:15 -05:00
Russell Hancox
02c1d0f267 Project: Bump version to 2022.3 (#745) 2022-03-10 09:35:44 -05:00
Pete Markowsky
4728c346cc Fix uninstall.sh to remove the metric & bundle services. (#743) 2022-03-09 18:00:45 -05:00
Pete Markowsky
9588dd8a0e Fix: Issue with SNTMetricHTTPWriter Timeouts (#741)
Fix issue with santametricservice timing out due to incorrect timeout argument.
2022-03-08 14:12:57 -05:00
88 changed files with 2249 additions and 3977 deletions

View File

@@ -12,7 +12,6 @@ jobs:
runs-on: ubuntu-latest
outputs:
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
build_driver: ${{ steps.step1.outputs.build_driver }}
steps:
- uses: actions/checkout@v2
@@ -26,16 +25,11 @@ jobs:
cat files.txt
build_driver=0
build_and_run_tests=0
for file in `cat files.txt`; do
if [[ $file = Source/* ]]; then
build_and_run_test=1;
if [[ $file = Source/santa_driver/* || $file = Source/common/* ]]; then
build_driver=1;
break;
fi
fi
done
@@ -46,13 +40,6 @@ jobs:
echo "::set-output name=run_build_and_tests::false"
fi
if [[ $build_driver != 0 ]]; then
echo "NEED TO BUILD DRIVER"
echo "::set-output name=build_driver::true"
else
echo "::set-output name=build_driver::false"
fi
lint:
runs-on: ubuntu-latest
needs: [preqs]
@@ -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
View File

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

View File

@@ -9,10 +9,8 @@
<choice id="default">
<pkg-ref id="com.google.santa"/>
<pkg-ref id="com.google.santa-driver"/>
</choice>
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
<pkg-ref id="com.google.santa-driver" active="system.compareVersions(my.target.systemVersion.ProductVersion, '10.15') &lt; 0">kext.pkg</pkg-ref>
</installer-gui-script>

View File

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

View File

@@ -14,7 +14,6 @@ mkdir -p /usr/local/bin
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
# Remove the kext before com.google.santa.daemon loads if the SystemExtension is already present.
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && rm -rf /Library/Extensions/santa-driver.kext
# Load com.google.santa.daemon, its main has logic to handle loading the kext

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
///
/// Store information about new allowlist rules for later logging.
///
@interface SNTAllowlistInfo : NSObject
@property pid_t pid;
@property int pidversion;
@property NSString *targetPath;
@property NSString *sha256;
- (instancetype)initWithPid:(pid_t)pid
pidversion:(int)pidver
targetPath:(NSString*)targetPath
sha256:(NSString*)hash;
@end

View File

@@ -0,0 +1,32 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTAllowlistInfo.h"
@implementation SNTAllowlistInfo
- (instancetype)initWithPid:(pid_t)pid
pidversion:(int)pidver
targetPath:(NSString *)targetPath
sha256:(NSString *)hash {
self = [super init];
if (self) {
_pid = pid;
_pidversion = pidver;
_targetPath = targetPath;
_sha256 = hash;
}
return self;
}
@end

View File

@@ -15,7 +15,7 @@
#import <Foundation/Foundation.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/common/SNTCommon.h"
@class MOLCertificate;

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,27 +13,12 @@
/// limitations under the License.
///
/// Logging definitions, for both kernel and user space.
/// Logging definitions
///
#ifndef SANTA__COMMON__LOGGING_H
#define SANTA__COMMON__LOGGING_H
#ifdef KERNEL
#include <IOKit/IOLib.h>
#ifdef DEBUG
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
#else // DEBUG
#define LOGD(format, ...)
#endif // DEBUG
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
#else // KERNEL
#ifdef __cplusplus
extern "C" {
#endif
@@ -68,6 +53,4 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
} // extern C
#endif
#endif // KERNEL
#endif // SANTA__COMMON__LOGGING_H

View File

@@ -14,13 +14,6 @@
#include "Source/common/SNTPrefixTree.h"
#ifdef KERNEL
#include <libkern/locks.h>
#include "Source/common/SNTLogging.h"
#else
#include <string.h>
#include <mutex>
@@ -46,25 +39,14 @@
#define lck_mtx_lock(l) l->lock()
#define lck_mtx_unlock(l) l->unlock()
#endif // KERNEL
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
root_ = new SantaPrefixNode();
node_count_ = 0;
max_nodes_ = max_nodes;
#ifdef KERNEL
spt_lock_grp_attr_ = lck_grp_attr_alloc_init();
spt_lock_grp_ =
lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
spt_lock_attr_ = lck_attr_alloc_init();
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
spt_add_lock_ = lck_mtx_alloc_init(spt_lock_grp_, spt_lock_attr_);
#else
pthread_rwlock_init(&spt_lock_, nullptr);
spt_add_lock_ = new std::mutex;
#endif
}
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
@@ -241,32 +223,5 @@ SNTPrefixTree::~SNTPrefixTree() {
root_ = nullptr;
lck_rw_unlock_exclusive(spt_lock_);
#ifdef KERNEL
if (spt_lock_) {
lck_rw_free(spt_lock_, spt_lock_grp_);
spt_lock_ = nullptr;
}
if (spt_add_lock_) {
lck_mtx_free(spt_add_lock_, spt_lock_grp_);
spt_add_lock_ = nullptr;
}
if (spt_lock_attr_) {
lck_attr_free(spt_lock_attr_);
spt_lock_attr_ = nullptr;
}
if (spt_lock_grp_) {
lck_grp_free(spt_lock_grp_);
spt_lock_grp_ = nullptr;
}
if (spt_lock_grp_attr_) {
lck_grp_attr_free(spt_lock_grp_attr_);
spt_lock_grp_attr_ = nullptr;
}
#else
pthread_rwlock_destroy(&spt_lock_);
#endif
}

View File

@@ -18,15 +18,11 @@
#include <IOKit/IOReturn.h>
#include <sys/param.h>
#ifdef KERNEL
#include <libkern/locks.h>
#else
// Support for unit testing.
#include <pthread.h>
#include <stdint.h>
#include <mutex>
#endif // KERNEL
///
/// SantaPrefixTree is a simple prefix tree implementation.
@@ -88,16 +84,8 @@ class SNTPrefixTree {
uint32_t max_nodes_;
uint32_t node_count_;
#ifdef KERNEL
lck_grp_t *spt_lock_grp_;
lck_grp_attr_t *spt_lock_grp_attr_;
lck_attr_t *spt_lock_attr_;
lck_rw_t *spt_lock_;
lck_mtx_t *spt_add_lock_;
#else // KERNEL
pthread_rwlock_t spt_lock_;
std::mutex *spt_add_lock_;
#endif // KERNEL
};
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */

View File

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

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "Source/common/SNTSystemInfo.h"
#include <sys/sysctl.h>
@implementation SNTSystemInfo
@@ -60,6 +61,13 @@
return @(hostname);
}
+ (NSString *)modelIdentifier {
char model[32];
size_t len = 32;
sysctlbyname("hw.model", model, &len, NULL, 0);
return @(model);
}
#pragma mark - Internal
+ (NSDictionary *)_systemVersionDictionary {

View File

@@ -27,13 +27,10 @@ NSString *const kBundleID = @"com.google.santa.daemon";
@implementation SNTXPCControlInterface
+ (NSString *)serviceID {
if ([[SNTConfigurator configurator] enableSystemExtension]) {
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
// "teamid.com.google.santa.daemon.xpc"
NSString *t = cs.signingInformation[@"teamid"];
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
}
return kBundleID;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
// "teamid.com.google.santa.daemon.xpc"
NSString *t = cs.signingInformation[@"teamid"];
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
}
+ (NSString *)systemExtensionID {

View File

@@ -16,7 +16,7 @@
#import <MOLCertificate/MOLCertificate.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/common/SNTCommon.h"
@class SNTRule;
@class SNTStoredEvent;

View File

@@ -20,28 +20,19 @@
#include <stdint.h>
#include <sys/cdefs.h>
#include "Source/common/SNTKernelCommon.h"
#ifdef KERNEL
#include <IOKit/IOLib.h>
#else // KERNEL
// Support for unit testing.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "Source/common/SNTCommon.h"
#define panic(args...) \
printf(args); \
printf("\n"); \
abort()
#define IOMallocAligned(sz, alignment) malloc(sz);
#define IOFreeAligned(addr, sz) free(addr)
#define OSTestAndSet OSAtomicTestAndSet
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif // KERNEL
/**
A type to specialize to help SantaCache with its hashing.
@@ -88,8 +79,7 @@ class SantaCache {
(1 << (32 -
__builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
if (unlikely(bucket_count_ > UINT32_MAX)) bucket_count_ = UINT32_MAX;
buckets_ = (struct bucket *)IOMallocAligned(
bucket_count_ * sizeof(struct bucket), 2);
buckets_ = (struct bucket *)malloc(bucket_count_ * sizeof(struct bucket));
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
}
@@ -98,7 +88,7 @@ class SantaCache {
*/
~SantaCache() {
clear();
IOFreeAligned(buckets_, bucket_count_ * sizeof(struct bucket));
free(buckets_);
}
/**
@@ -173,7 +163,7 @@ class SantaCache {
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
struct entry *next_entry = entry->next;
IOFreeAligned(entry, sizeof(struct entry));
free(entry);
entry = next_entry;
}
}
@@ -284,8 +274,8 @@ class SantaCache {
} else {
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
}
IOFreeAligned(entry, sizeof(struct entry));
OSDecrementAtomic(&count_);
free(entry);
OSAtomicDecrement64((volatile int64_t *)&count_);
}
unlock(bucket);
@@ -318,14 +308,13 @@ class SantaCache {
// Allocate a new entry, set the key and value, then put this new entry at
// the head of this bucket's linked list.
struct entry *new_entry =
(struct entry *)IOMallocAligned(sizeof(struct entry), 2);
struct entry *new_entry = (struct entry *)malloc(sizeof(struct entry));
bzero(new_entry, sizeof(struct entry));
new_entry->key = key;
new_entry->value = value;
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
OSIncrementAtomic(&count_);
OSAtomicIncrement64((volatile int64_t *)&count_);
unlock(bucket);
return true;
@@ -335,7 +324,7 @@ class SantaCache {
Lock a bucket. Spins until the lock is acquired.
*/
inline void lock(struct bucket *bucket) const {
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head))
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head))
;
}
@@ -343,7 +332,8 @@ class SantaCache {
Unlock a bucket. Panics if the lock wasn't locked.
*/
inline void unlock(struct bucket *bucket) const {
if (unlikely(OSTestAndClear(7, (volatile uint8_t *)&bucket->head))) {
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
0)) {
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
}
}
@@ -375,8 +365,6 @@ class SantaCache {
}
};
#ifndef KERNEL
#pragma clang diagnostic pop
#endif
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H

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

@@ -0,0 +1,145 @@
//
// !!! WARNING !!!
// This proto is in beta format and subject to change.
//
syntax = "proto3";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
option objc_class_prefix = "SNTPB";
package santa;
message ProcessInfo {
optional int32 pid = 1;
optional int32 pidversion = 2;
optional int32 ppid = 3;
optional int32 uid = 4;
optional string user = 5;
optional int32 gid = 6;
optional string group = 7;
}
message FileModification {
enum Action {
ACTION_UNKNOWN = 0;
ACTION_DELETE = 1;
ACTION_EXCHANGE = 2;
ACTION_LINK = 3;
ACTION_RENAME = 4;
ACTION_WRITE = 5;
}
optional Action action = 1;
optional string path = 2;
optional string newpath = 3;
optional string process = 4;
optional string process_path = 5;
optional ProcessInfo process_info = 6;
optional string machine_id = 7;
}
message Execution {
enum Decision {
DECISION_UNKNOWN = 0;
DECISION_ALLOW = 1;
DECISION_DENY = 2;
}
enum Reason {
REASON_UNKNOWN = 0;
REASON_BINARY = 1;
REASON_CERT = 2;
REASON_COMPILER = 3;
REASON_NOT_RUNNING = 4;
REASON_PENDING_TRANSITIVE = 5;
REASON_SCOPE = 6;
REASON_TEAM_ID = 7;
REASON_TRANSITIVE = 8;
}
enum Mode {
MODE_UNKNOWN = 0;
MODE_LOCKDOWN = 1;
MODE_MONITOR = 2;
}
optional Decision decision = 1;
optional Reason reason = 2;
optional string explain = 3;
optional string sha256 = 4;
optional string cert_sha256 = 5;
optional string cert_cn = 6;
optional string quarantine_url = 7;
optional ProcessInfo process_info = 8;
optional Mode mode = 9;
optional string path = 10;
optional string original_path = 11;
repeated string args = 12;
optional string machine_id = 13;
}
message DiskAppeared {
optional string mount = 1;
optional string volume = 2;
optional string bsd_name = 3;
optional string fs = 4;
optional string model = 5;
optional string serial = 6;
optional string bus = 7;
optional string dmg_path = 8;
optional string appearance = 9;
}
message DiskDisappeared {
optional string mount = 1;
optional string volume = 2;
optional string bsd_name = 3;
}
message Bundle {
// This is the hash of the file within the bundle that triggered the event
optional string sha256 = 1;
// This is the hash of the hashes of all executables in the bundle
optional string bundle_hash = 2;
optional string bundle_name = 3;
optional string bundle_id = 4;
optional string bundle_path = 5;
optional string path = 6;
}
message Fork {
optional ProcessInfo process_info = 1;
}
message Exit {
optional ProcessInfo process_info = 1;
}
message Allowlist {
optional int32 pid = 1;
optional int32 pidversion = 2;
optional string path = 3;
optional string sha256 = 4;
}
message SantaMessage {
google.protobuf.Timestamp event_time = 1;
oneof message {
FileModification file_modification = 2;
Execution execution = 3;
DiskAppeared disk_appeared = 4;
DiskDisappeared disk_disappeared = 5;
Bundle bundle = 6;
Fork fork = 7;
Exit exit = 8;
Allowlist allowlist = 9;
}
}
message LogBatch {
repeated google.protobuf.Any records = 1;
}

View File

@@ -67,10 +67,6 @@ int main(int argc, const char *argv[]) {
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
OSSystemExtensionRequest *req;
if (sysxOperation.intValue == 1) {
if (![[SNTConfigurator configurator] enableSystemExtension]) {
NSLog(@"EnableSystemExtension is disabled");
exit(1);
}
NSLog(@"Requesting SystemExtension activation");
req = [OSSystemExtensionRequest activationRequestForExtension:e queue:q];
} else if (sysxOperation.intValue == 2) {

View File

@@ -1,93 +0,0 @@
load(
"@build_bazel_rules_apple//apple:macos.bzl",
"macos_command_line_application",
"macos_kernel_extension",
)
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
licenses(["notice"])
cc_library(
name = "santa_driver_lib",
srcs = [
"SantaDecisionManager.cc",
"SantaDecisionManager.h",
"SantaDriver.cc",
"SantaDriver.h",
"SantaDriverClient.cc",
"SantaDriverClient.h",
"main.cc",
],
copts = [
"-mkernel",
"-fapple-kext",
"-Wno-ossharedptr-misuse",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = [
"KERNEL",
"APPLE",
"NeXT",
"SANTA_VERSION=" + SANTA_VERSION,
],
deps = [
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLoggingKernel",
"//Source/common:SNTPrefixTreeKernel",
"//Source/common:SantaCache",
],
alwayslink = 1,
)
macos_kernel_extension(
name = "santa_driver",
bundle_id = "com.google.santa-driver",
bundle_name = "santa-driver",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santa_driver_lib"],
)
objc_library(
name = "kernel_tests_lib",
srcs = ["kernel_tests.mm"],
sdk_frameworks = [
"Foundation",
"IOKit",
],
deps = ["//Source/common:SNTKernelCommon"],
)
macos_command_line_application(
name = "kernel_tests_bin",
bundle_id = "com.google.santa.KernelTests",
minimum_os_version = "10.9",
deps = [":kernel_tests_lib"],
)
run_command(
name = "kernel_tests",
srcs = [
":kernel_tests_bin",
":santa_driver",
],
cmd = """
env
function sigint() {
echo "\nInterrupted, unloading driver."
sudo kextunload -b com.google.santa-driver >/dev/null
exit 1
}
unzip -o $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
echo "Launching Kernel Tests as root. You may be prompted for your sudo password."
trap sigint INT
sudo $${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/kernel_tests_bin
echo "Tests complete."
if kextstat | grep com.google.santa-driver; then
sudo kextunload -b com.google.santa-driver >/dev/null
fi
""",
)

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>santa-driver</string>
<key>CFBundleIdentifier</key>
<string>com.google.santa-driver</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>santa-driver</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>IOKitPersonalities</key>
<dict>
<key>SantaDriver</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.google.santa-driver</string>
<key>IOClass</key>
<string>com_google_SantaDriver</string>
<key>IOMatchCategory</key>
<string>com_google_SantaDriver</string>
<key>IOProviderClass</key>
<string>IOResources</string>
<key>IOResourceMatch</key>
<string>IOKit</string>
<key>IOUserClientClass</key>
<string>com_google_SantaDriverClient</string>
</dict>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Google LLC.</string>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.bsd</key>
<string>9.0.0</string>
<key>com.apple.kpi.iokit</key>
<string>9.0.0</string>
<key>com.apple.kpi.libkern</key>
<string>9.0.0</string>
<key>com.apple.kpi.mach</key>
<string>9.0.0</string>
</dict>
</dict>
</plist>

View File

@@ -1,843 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santa_driver/SantaDecisionManager.h"
// This is a made-up KAUTH_FILEOP constant which represents a
// KAUTH_VNODE_WRITE_DATA event that gets passed to SantaDecisionManager's
// FileOpCallback method. The KAUTH_FILEOP_* constants are defined in
// sys/kauth.h and run from 1--7. KAUTH_VNODE_WRITE_DATA is already defined as
// 4 so it overlaps with the other KAUTH_FILEOP_* constants and can't be used.
// We define KAUTH_FILEOP_WRITE as something much greater than 7.
#define KAUTH_FILEOP_WRITE 100
#define super OSObject
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
#pragma mark Object Lifecycle
template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t) {
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
}
bool SantaDecisionManager::init() {
if (!super::init()) return false;
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
sdm_lock_attr_ = lck_attr_alloc_init();
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(5000, 2);
non_root_decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(500, 2);
vnode_pid_map_ = new SantaCache<santa_vnode_id_t, uint64_t>(2000, 5);
compiler_pid_set_ = new SantaCache<pid_t, pid_t>(500, 5);
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
if (!decision_dataqueue_) return kIOReturnNoMemory;
log_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxLogQueueEvents, sizeof(santa_message_t));
if (!log_dataqueue_) return kIOReturnNoMemory;
client_pid_ = 0;
root_fsid_ = 0;
// Setup file modification prefix filter.
filemod_prefix_filter_ = new SNTPrefixTree();
return true;
}
void SantaDecisionManager::free() {
delete root_decision_cache_;
delete non_root_decision_cache_;
delete vnode_pid_map_;
StopPidMonitorThreads();
if (decision_dataqueue_lock_) {
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
decision_dataqueue_lock_ = nullptr;
}
if (log_dataqueue_lock_) {
lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
log_dataqueue_lock_ = nullptr;
}
if (sdm_lock_attr_) {
lck_attr_free(sdm_lock_attr_);
sdm_lock_attr_ = nullptr;
}
if (sdm_lock_grp_) {
lck_grp_free(sdm_lock_grp_);
sdm_lock_grp_ = nullptr;
}
if (sdm_lock_grp_attr_) {
lck_grp_attr_free(sdm_lock_grp_attr_);
sdm_lock_grp_attr_ = nullptr;
}
OSSafeReleaseNULL(decision_dataqueue_);
OSSafeReleaseNULL(log_dataqueue_);
delete filemod_prefix_filter_;
super::free();
}
#pragma mark Client Management
void SantaDecisionManager::ConnectClient(pid_t pid) {
if (!pid) return;
client_pid_ = pid;
// Determine root fsid
vfs_context_t ctx = vfs_context_create(nullptr);
if (ctx) {
vnode_t root = vfs_rootvnode();
if (root) {
root_fsid_ = GetVnodeIDForVnode(ctx, root).fsid;
vnode_put(root);
}
vfs_context_rele(ctx);
}
// Any decisions made while the daemon wasn't
// connected should be cleared
ClearCache();
failed_decision_queue_requests_ = 0;
failed_log_queue_requests_ = 0;
}
void SantaDecisionManager::DisconnectClient(bool itDied, pid_t pid) {
if (client_pid_ == 0 || (pid > 0 && pid != client_pid_)) return;
client_pid_ = 0;
// Ask santad to shutdown, in case it's running.
if (!itDied) {
auto message = new santa_message_t;
message->action = ACTION_REQUEST_SHUTDOWN;
PostToDecisionQueue(message);
delete message;
decision_dataqueue_->setNotificationPort(nullptr);
} else {
// If the client died, reset the data queues so when it reconnects
// it doesn't get swamped straight away.
lck_mtx_lock(decision_dataqueue_lock_);
decision_dataqueue_->release();
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
lck_mtx_unlock(decision_dataqueue_lock_);
lck_mtx_lock(log_dataqueue_lock_);
log_dataqueue_->release();
log_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxLogQueueEvents, sizeof(santa_message_t));
lck_mtx_unlock(log_dataqueue_lock_);
}
// Reset the filter.
// On startup the daemon will add prefixes as needed.
FilemodPrefixFilterReset();
}
bool SantaDecisionManager::ClientConnected() const {
if (client_pid_ <= 0) return false;
auto p = proc_find(client_pid_);
auto is_exiting = false;
if (p) {
is_exiting = proc_exiting(p);
proc_rele(p);
}
return (client_pid_ > 0 && !is_exiting);
}
void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
lck_mtx_lock(decision_dataqueue_lock_);
decision_dataqueue_->setNotificationPort(port);
lck_mtx_unlock(decision_dataqueue_lock_);
}
void SantaDecisionManager::SetLogPort(mach_port_t port) {
lck_mtx_lock(log_dataqueue_lock_);
log_dataqueue_->setNotificationPort(port);
lck_mtx_unlock(log_dataqueue_lock_);
}
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
lck_mtx_lock(decision_dataqueue_lock_);
IOMemoryDescriptor *r = decision_dataqueue_->getMemoryDescriptor();
lck_mtx_unlock(decision_dataqueue_lock_);
return r;
}
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
lck_mtx_lock(log_dataqueue_lock_);
IOMemoryDescriptor *r = log_dataqueue_->getMemoryDescriptor();
lck_mtx_unlock(log_dataqueue_lock_);
return r;
}
#pragma mark Listener Control
kern_return_t SantaDecisionManager::StartListener() {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
vnode_listener_ = kauth_listen_scope(
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
#pragma clang diagnostic pop
if (!vnode_listener_) return kIOReturnInternalError;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
fileop_listener_ = kauth_listen_scope(
KAUTH_SCOPE_FILEOP, fileop_scope_callback,
reinterpret_cast<void *>(this));
#pragma clang diagnostic pop
if (!fileop_listener_) return kIOReturnInternalError;
LOGD("Listeners started.");
return kIOReturnSuccess;
}
kern_return_t SantaDecisionManager::StopListener() {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
kauth_unlisten_scope(vnode_listener_);
vnode_listener_ = nullptr;
kauth_unlisten_scope(fileop_listener_);
fileop_listener_ = nullptr;
#pragma clang diagnostic pop
// Wait for any active invocations to finish before returning
do {
IOSleep(5);
} while (listener_invocations_);
// Delete any cached decisions
ClearCache();
LOGD("Listeners stopped.");
return kIOReturnSuccess;
}
# pragma mark Monitoring PIDs
// Arguments that are passed to pid_monitor thread.
typedef struct {
pid_t pid; // process to monitor
SantaDecisionManager *sdm; // reference to SantaDecisionManager
} pid_monitor_info;
// Function executed in its own thread used to monitor a compiler process for
// termination and then remove the process pid from cache of compiler pids.
static void pid_monitor(void *param, __unused wait_result_t wait_result) {
pid_monitor_info *info = (pid_monitor_info *)param;
if (info && info->sdm) {
uint32_t sleep_time = info->sdm->PidMonitorSleepTimeMilliseconds();
while (!info->sdm->PidMonitorThreadsShouldExit()) {
proc_t proc = proc_find(info->pid);
if (!proc) break;
proc_rele(proc);
IOSleep(sleep_time);
}
info->sdm->ForgetCompilerPid(info->pid);
info->sdm->DecrementPidMonitorThreadCount();
}
thread_terminate(current_thread());
}
// TODO(nguyenphillip): Look at moving pid monitoring out of SDM entirely,
// maybe by creating a dedicated class to do this that SDM could then query.
void SantaDecisionManager::MonitorCompilerPidForExit(pid_t pid) {
// Don't start any new threads if compiler_pid_set_ doesn't exist.
if (!compiler_pid_set_) return;
auto info = new pid_monitor_info;
info->pid = pid;
info->sdm = this;
thread_t thread = THREAD_NULL;
IncrementPidMonitorThreadCount();
if (KERN_SUCCESS != kernel_thread_start(pid_monitor, (void *)info, &thread)) {
LOGE("couldn't start pid monitor thread");
DecrementPidMonitorThreadCount();
}
thread_deallocate(thread);
}
void SantaDecisionManager::ForgetCompilerPid(pid_t pid) {
if (compiler_pid_set_) compiler_pid_set_->remove(pid);
}
bool SantaDecisionManager::PidMonitorThreadsShouldExit() const {
return compiler_pid_set_ == nullptr;
}
bool SantaDecisionManager::StopPidMonitorThreads() {
// Each pid_monitor thread checks for the existence of compiler_pid_set_.
// As soon as they see that it's gone, they should terminate and decrement
// SantaDecisionManager's pid_monitor_thread_count. When this count decreases
// to zero all threads have finished.
auto temp = compiler_pid_set_;
compiler_pid_set_ = nullptr;
delete temp;
// Sleep time between checks starts at 10 ms, but increases to 5 sec after
// 10 sec have passed without the thread count dropping to 0.
unsigned int sleep_time_milliseconds = 10;
unsigned int total_wait_time = 0;
while (pid_monitor_thread_count_ > 0) {
if (sleep_time_milliseconds == 10) {
total_wait_time += sleep_time_milliseconds;
if (total_wait_time >= 10000) {
sleep_time_milliseconds = 5000;
LOGD("Waited %d ms for pid monitor threads to quit, switching sleep"
"time to %d ms", total_wait_time, sleep_time_milliseconds);
}
}
IOSleep(sleep_time_milliseconds);
}
LOGD("Pid monitor threads stopped.");
return true;
}
uint32_t SantaDecisionManager::PidMonitorSleepTimeMilliseconds() const {
return kPidMonitorSleepTimeMilliseconds;
}
#pragma mark Cache Management
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<santa_vnode_id_t, uint64_t> *SantaDecisionManager::CacheForIdentifier(
const santa_vnode_id_t identifier) {
return (identifier.fsid == root_fsid_) ? root_decision_cache_ : non_root_decision_cache_;
}
void SantaDecisionManager::AddToCache(
santa_vnode_id_t identifier, santa_action_t decision, uint64_t microsecs) {
auto decision_cache = CacheForIdentifier(identifier);
switch (decision) {
case ACTION_REQUEST_BINARY:
decision_cache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
break;
case ACTION_RESPOND_ACK:
decision_cache->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_COMPILER:
case ACTION_RESPOND_DENY: {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
if (!decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
break;
}
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
decision_cache->set(identifier, val, 0);
break;
}
default:
break;
}
wakeup((void *)identifier.unsafe_simple_id());
}
void SantaDecisionManager::RemoveFromCache(santa_vnode_id_t identifier) {
if (unlikely(identifier.fsid == 0 && identifier.fileid == 0)) return;
CacheForIdentifier(identifier)->remove(identifier);
wakeup((void *)identifier.unsafe_simple_id());
}
uint64_t SantaDecisionManager::RootCacheCount() const {
return root_decision_cache_->count();
}
uint64_t SantaDecisionManager::NonRootCacheCount() const {
return non_root_decision_cache_->count();
}
void SantaDecisionManager::ClearCache(bool non_root_only) {
if (!non_root_only) root_decision_cache_->clear();
non_root_decision_cache_->clear();
}
void SantaDecisionManager::CacheBucketCount(
uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
root_decision_cache_->bucket_counts(per_bucket_counts, array_size, start_bucket);
}
#pragma mark Decision Fetching
santa_action_t SantaDecisionManager::GetFromCache(santa_vnode_id_t identifier) {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
auto decision_cache = CacheForIdentifier(identifier);
uint64_t cache_val = decision_cache->get(identifier);
if (cache_val == 0) return result;
// Decision is stored in upper 8 bits, timestamp in remaining 56.
result = (santa_action_t)(cache_val >> 56);
decision_time = (cache_val & ~(0xFF00000000000000));
if (RESPONSE_VALID(result)) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache->remove(identifier);
return ACTION_UNSET;
}
}
}
return result;
}
santa_action_t SantaDecisionManager::GetFromDaemon(
santa_message_t *message, santa_vnode_id_t identifier) {
auto return_action = ACTION_UNSET;
#ifdef DEBUG
clock_sec_t secs = 0;
clock_usec_t microsecs = 0;
clock_get_system_microtime(&secs, &microsecs);
uint64_t uptime = (secs * 1000000) + microsecs;
#endif
// Wait for the daemon to respond or die.
do {
// Add pending request to cache, to be replaced
// by daemon with actual response.
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
// Send request to daemon.
if (!PostToDecisionQueue(message)) {
LOGE("Failed to queue request for %s.", message->path);
RemoveFromCache(identifier);
return ACTION_ERROR;
}
// Check the cache every kRequestLoopSleepMilliseconds. Break this loop and send the request
// again if kRequestCacheChecks is reached. Don't break the loop if the daemon is working on the
// request, indicated with ACTION_RESPOND_ACK.
auto cache_check_count = 0;
do {
msleep((void *)message->vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} while (ClientConnected() &&
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
|| (return_action == ACTION_RESPOND_ACK)));
} while (!RESPONSE_VALID(return_action) && ClientConnected());
// If response is still not valid, the daemon exited
if (!RESPONSE_VALID(return_action)) {
LOGE("Daemon process did not respond correctly. Allowing executions "
"until it comes back. Executable path: %s", message->path);
RemoveFromCache(identifier);
return ACTION_ERROR;
}
#ifdef DEBUG
clock_get_system_microtime(&secs, &microsecs);
LOGD("Decision time: %4lldms (%s)",
(((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
#endif
return return_action;
}
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t cred,
const vnode_t vp,
const santa_vnode_id_t vnode_id) {
while (true) {
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
// Check to see if item is in cache
auto return_action = GetFromCache(vnode_id);
// If item was in cache with a valid response, return it.
// If item is in cache but hasn't received a response yet, sleep for a bit.
// If item is not in cache, break out of loop to send request to daemon.
if (RESPONSE_VALID(return_action)) {
return return_action;
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
// until AddToCache is called, indicating a response has arrived.
msleep((void *)vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
} else {
break;
}
}
// Get path
char path[MAXPATHLEN];
int name_len = MAXPATHLEN;
path[MAXPATHLEN - 1] = 0;
if (vn_getpath(vp, path, &name_len) == ENOSPC) {
return ACTION_RESPOND_TOOLONG;
}
auto message = NewMessage(cred);
strlcpy(message->path, path, sizeof(message->path));
message->action = ACTION_REQUEST_BINARY;
message->vnode_id = vnode_id;
proc_name(message->ppid, message->pname, sizeof(message->pname));
auto return_action = GetFromDaemon(message, vnode_id);
delete message;
return return_action;
}
#pragma mark Misc
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
lck_mtx_lock(decision_dataqueue_lock_);
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (++failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
LOGE("Failed to queue more than %d decision requests, killing daemon",
kMaxDecisionQueueFailures);
proc_signal(client_pid_, SIGKILL);
}
}
lck_mtx_unlock(decision_dataqueue_lock_);
return kr;
}
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
lck_mtx_lock(log_dataqueue_lock_);
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (failed_log_queue_requests_++ == 0) {
LOGW("Dropping log queue messages");
}
} else {
if (failed_log_queue_requests_ > 0) {
failed_log_queue_requests_--;
}
}
lck_mtx_unlock(log_dataqueue_lock_);
return kr;
}
#pragma mark Invocation Tracking & PID comparison
void SantaDecisionManager::IncrementListenerInvocations() {
OSIncrementAtomic(&listener_invocations_);
}
void SantaDecisionManager::DecrementListenerInvocations() {
OSDecrementAtomic(&listener_invocations_);
}
void SantaDecisionManager::IncrementPidMonitorThreadCount() {
OSIncrementAtomic(&pid_monitor_thread_count_);
}
void SantaDecisionManager::DecrementPidMonitorThreadCount() {
OSDecrementAtomic(&pid_monitor_thread_count_);
}
bool SantaDecisionManager::IsCompilerProcess(pid_t pid) {
for (;;) {
// Find the parent pid.
proc_t proc = proc_find(pid);
if (!proc) return false;
pid_t ppid = proc_ppid(proc);
proc_rele(proc);
// Quit if process is launchd or has no parent.
if (ppid == 0 || pid == ppid) break;
pid_t val = compiler_pid_set_->get(pid);
// If pid was in compiler_pid_set_ then make sure that it has the same
// parent pid as when it was set.
if (val) return val == ppid;
// If pid not in the set, then quit unless we want to check ancestors.
if (!kCheckCompilerAncestors) break;
pid = ppid;
}
return false;
}
#pragma mark Callbacks
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
const vfs_context_t ctx,
const vnode_t vp,
int *errno) {
// Get ID for the vnode
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
if (vnode_id.fsid == 0 && vnode_id.fileid == 0) return KAUTH_RESULT_DEFER;
// Fetch decision
auto returnedAction = FetchDecision(cred, vp, vnode_id);
switch (returnedAction) {
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_COMPILER:
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
auto proc = vfs_context_proc(ctx);
if (proc) {
pid_t pid = proc_pid(proc);
pid_t ppid = proc_ppid(proc);
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
uint64_t val = ((uint64_t)pid << 32) | ((uint64_t)ppid & 0xFFFFFFFF);
vnode_pid_map_->set(vnode_id, val);
if (returnedAction == ACTION_RESPOND_ALLOW_COMPILER && ppid != 0) {
// Do some additional bookkeeping for compilers:
// We associate the pid with a compiler so that when we see it later
// in the context of a KAUTH_FILEOP event, we'll recognize it.
compiler_pid_set_->set(pid, ppid);
// And start polling for the compiler process termination, so that we
// can remove the pid from our cache of compiler pids.
MonitorCompilerPidForExit(pid);
}
}
return KAUTH_RESULT_ALLOW;
}
case ACTION_RESPOND_DENY:
*errno = EPERM;
return KAUTH_RESULT_DENY;
case ACTION_RESPOND_TOOLONG:
*errno = ENAMETOOLONG;
return KAUTH_RESULT_DENY;
default:
// NOTE: Any unknown response or error condition causes us to fail open.
// Whilst from a security perspective this is bad, it's important that
// we don't break user's machines.
return KAUTH_RESULT_DEFER;
}
}
void SantaDecisionManager::FileOpCallback(
const kauth_action_t action, const vnode_t vp,
const char *path, const char *new_path) {
if (!ClientConnected()) return;
// KAUTH_FILEOP_CLOSE implies KAUTH_FILEOP_CLOSE_MODIFIED, so remove it from the cache.
if (action == KAUTH_FILEOP_CLOSE) {
auto context = vfs_context_create(nullptr);
RemoveFromCache(GetVnodeIDForVnode(context, vp));
vfs_context_rele(context);
}
// Don't log santad fileops.
if (proc_selfpid() == client_pid_) return;
if (vp && action == KAUTH_FILEOP_EXEC) {
auto context = vfs_context_create(nullptr);
auto vnode_id = GetVnodeIDForVnode(context, vp);
vfs_context_rele(context);
auto message = NewMessage(nullptr);
message->vnode_id = vnode_id;
message->action = ACTION_NOTIFY_EXEC;
strlcpy(message->path, path, sizeof(message->path));
// The vnode scope gets posix_spawn pid and ppid properly. The fileop scope does not.
// Get pid and ppid cached during vnode execution.
uint64_t val = vnode_pid_map_->get(vnode_id);
if (val) {
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
message->pid = (pid_t)(val >> 32);
message->ppid = (pid_t)(val & ~0xFFFFFFFF00000000);
}
PostToLogQueue(message);
delete message;
return;
}
// For transitive whitelisting decisions, we must check for KAUTH_FILEOP_CLOSE events from a
// known compiler process. But we must also check for KAUTH_FILEOP_RENAME events because clang
// under Xcode 9 will, if the output file already exists, write to a temp file, delete the
// existing file, then rename the temp file, without ever closing it. So in this scenario,
// the KAUTH_FILEOP_RENAME is the only chance we have of whitelisting the output.
if (action == KAUTH_FILEOP_CLOSE || (action == KAUTH_FILEOP_RENAME && new_path)) {
auto message = NewMessage(nullptr);
if (IsCompilerProcess(message->pid)) {
// Fill out the rest of the message details and send it to the decision queue.
auto context = vfs_context_create(nullptr);
vnode_t real_vp = vp;
// We have to manually look up the vnode pointer from new_path for KAUTH_FILEOP_RENAME.
if (!real_vp && new_path && ERR_SUCCESS == vnode_lookup(new_path, 0, &real_vp, context)) {
vnode_put(real_vp);
}
if (real_vp) message->vnode_id = GetVnodeIDForVnode(context, real_vp);
vfs_context_rele(context);
message->action = ACTION_NOTIFY_WHITELIST;
const char *real_path = (action == KAUTH_FILEOP_CLOSE) ? path : new_path;
strlcpy(message->path, real_path, sizeof(message->path));
proc_name(message->pid, message->pname, sizeof(message->pname));
PostToDecisionQueue(message);
// Add a temporary allow rule to the decision cache for this vnode_id
// while SNTCompilerController decides whether or not to add a
// permanent rule for the new file to the rules database. This is
// because checking if the file is a Mach-O binary and hashing it might
// not finish before an attempt to execute it.
AddToCache(message->vnode_id, ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE, 0);
}
delete message;
// Don't need to do anything else for FILEOP_CLOSE, but FILEOP_RENAME should fall through.
if (action == KAUTH_FILEOP_CLOSE) return;
}
// Filter out modifications to locations that are definitely
// not useful or made by santad.
if (!filemod_prefix_filter_->HasPrefix(path)) {
auto message = NewMessage(nullptr);
strlcpy(message->path, path, sizeof(message->path));
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
proc_name(message->pid, message->pname, sizeof(message->pname));
switch (action) {
case KAUTH_FILEOP_WRITE:
// This is actually a KAUTH_VNODE_WRITE_DATA event.
message->action = ACTION_NOTIFY_WRITE;
break;
case KAUTH_FILEOP_RENAME:
message->action = ACTION_NOTIFY_RENAME;
break;
case KAUTH_FILEOP_LINK:
message->action = ACTION_NOTIFY_LINK;
break;
case KAUTH_FILEOP_EXCHANGE:
message->action = ACTION_NOTIFY_EXCHANGE;
break;
case KAUTH_FILEOP_DELETE:
message->action = ACTION_NOTIFY_DELETE;
break;
default:
delete message;
return;
}
PostToLogQueue(message);
delete message;
}
}
#undef super
extern "C" int fileop_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
if (unlikely(sdm == nullptr)) {
LOGE("fileop_scope_callback called with no decision manager");
return KAUTH_RESULT_DEFER;
}
vnode_t vp = nullptr;
char *path = nullptr;
char *new_path = nullptr;
switch (action) {
case KAUTH_FILEOP_CLOSE:
// We only care about KAUTH_FILEOP_CLOSE events where the closed file
// was modified.
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED))
return KAUTH_RESULT_DEFER;
// Intentional fallthrough to get vnode reference.
[[fallthrough]];
case KAUTH_FILEOP_DELETE:
case KAUTH_FILEOP_EXEC:
vp = reinterpret_cast<vnode_t>(arg0);
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
path = reinterpret_cast<char *>(arg1);
break;
case KAUTH_FILEOP_RENAME:
case KAUTH_FILEOP_EXCHANGE:
case KAUTH_FILEOP_LINK:
path = reinterpret_cast<char *>(arg0);
new_path = reinterpret_cast<char *>(arg1);
break;
default:
return KAUTH_RESULT_DEFER;
}
sdm->IncrementListenerInvocations();
sdm->FileOpCallback(action, vp, path, new_path);
sdm->DecrementListenerInvocations();
return KAUTH_RESULT_DEFER;
}
extern "C" int vnode_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
if (unlikely(sdm == nullptr)) {
LOGE("vnode_scope_callback called with no decision manager");
return KAUTH_RESULT_DEFER;
}
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
// We only care about regular files.
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
if ((action & (int)KAUTH_VNODE_EXECUTE) && !(action & (int)KAUTH_VNODE_ACCESS)) {
sdm->IncrementListenerInvocations();
int result = sdm->VnodeCallback(credential,
reinterpret_cast<vfs_context_t>(arg0),
vp,
reinterpret_cast<int *>(arg3));
sdm->DecrementListenerInvocations();
return result;
} else if (action & (int)KAUTH_VNODE_WRITE_DATA || action & (int)KAUTH_VNODE_APPEND_DATA) {
sdm->IncrementListenerInvocations();
if (!(action & (int)KAUTH_VNODE_ACCESS)) {
auto vnode_id = sdm->GetVnodeIDForVnode(reinterpret_cast<vfs_context_t>(arg0), vp);
sdm->RemoveFromCache(vnode_id);
}
char path[MAXPATHLEN];
int pathlen = MAXPATHLEN;
vn_getpath(vp, path, &pathlen);
// KAUTH_VNODE_WRITE_DATA events are translated into fake KAUTH_FILEOP_WRITE
// events so that we can handle them in the FileOpCallback function.
sdm->FileOpCallback(KAUTH_FILEOP_WRITE, vp, path, nullptr);
sdm->DecrementListenerInvocations();
}
return KAUTH_RESULT_DEFER;
}

View File

@@ -1,422 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#define SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#include <IOKit/IODataQueueShared.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOSharedDataQueue.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTLogging.h"
#include "Source/common/SNTPrefixTree.h"
#include "Source/common/SantaCache.h"
///
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
/// and responding to the request appropriately.
///
/// Documentation on the Kauth parts can be found here:
/// https://developer.apple.com/library/mac/technotes/tn2127/_index.html
///
class SantaDecisionManager : public OSObject {
OSDeclareDefaultStructors(SantaDecisionManager);
public:
/// Used for initialization after instantiation.
bool init() override;
/// Called automatically when retain count drops to 0.
void free() override;
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the decision queue.
*/
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the logging queue.
*/
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
/**
Called by SantaDriverClient when a client connects to the decision queue,
providing the pid of the client process.
*/
void ConnectClient(pid_t pid);
/// Called by SantaDriverClient when a client disconnects
void DisconnectClient(bool itDied = false, pid_t pid = proc_selfpid());
/// Returns whether a client is currently connected or not.
bool ClientConnected() const;
/// Sets the Mach port for notifying the decision queue.
void SetDecisionPort(mach_port_t port);
/// Sets the Mach port for notifying the log queue.
void SetLogPort(mach_port_t port);
/// Starts the kauth listeners.
kern_return_t StartListener();
/**
Stops the kauth listeners. After stopping new callback requests, waits
until all current invocations have finished before clearing the cache and
returning.
*/
kern_return_t StopListener();
/**
This spins off a new thread for each process that we monitor. Generally the
threads should be short-lived, since they die as soon as their associated
compiler process dies.
*/
void MonitorCompilerPidForExit(pid_t pid);
/// Remove the given pid from cache of compiler pids.
void ForgetCompilerPid(pid_t pid);
/// Returns true when SantaDecisionManager wants monitor threads to exit.
bool PidMonitorThreadsShouldExit() const;
/**
Stops the pid monitor threads. Waits until all threads have stopped before
returning. This also frees the compiler_pid_set_. Returns true if all
threads exited cleanly. Returns false if timed out while waiting.
*/
bool StopPidMonitorThreads();
/// Returns how long pid monitor should sleep between termination checks.
uint32_t PidMonitorSleepTimeMilliseconds() const;
/// Adds a decision to the cache, with a timestamp.
void AddToCache(santa_vnode_id_t identifier, const santa_action_t decision,
const uint64_t microsecs = GetCurrentUptime());
/**
Fetches a response from the cache, first checking to see if the entry
has expired.
*/
santa_action_t GetFromCache(santa_vnode_id_t identifier);
/// Checks to see if a given identifier is in the cache and removes it.
void RemoveFromCache(santa_vnode_id_t identifier);
/// Returns the number of entries in the cache.
uint64_t RootCacheCount() const;
uint64_t NonRootCacheCount() const;
/**
Clears the cache(s). If non_root_only is true, only the non-root cache
is cleared.
*/
void ClearCache(bool non_root_only = false);
/**
Fills out the per_bucket_counts array with the number of items in each
bucket in the non-root decision cache.
@param per_bucket_counts An array of uint16_t's to fill in with the number
of items in each bucket. The size of this array is expected to equal
array_size.
@param array_size The size of the per_bucket_counts array on input. Upon
return this will be updated to the number of slots that were actually used.
@param start_bucket If non-zero this is the bucket in the cache to start
from. Upon return this will be the next numbered bucket to start from for
subsequent requests.
*/
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size,
uint64_t *start_bucket);
/// Increments the count of active callbacks pending.
void IncrementListenerInvocations();
/// Decrements the count of active callbacks pending.
void DecrementListenerInvocations();
/// Increments the count of active pid monitor threads.
void IncrementPidMonitorThreadCount();
/// Decrements the count of active pid monitor threads.
void DecrementPidMonitorThreadCount();
/**
Determine if pid belongs to a compiler process. When
kCheckCompilerAncestors is set to true, this also checks all ancestor
processes of the pid.
*/
bool IsCompilerProcess(pid_t pid);
/**
Add a file modification prefix filter.
*/
inline IOReturn FilemodPrefixFilterAdd(const char *prefix,
uint64_t *node_count = nullptr) {
return filemod_prefix_filter_->AddPrefix(prefix, node_count);
}
/**
Reset the file modification prefix filter tree.
*/
inline void FilemodPrefixFilterReset() { filemod_prefix_filter_->Reset(); }
/**
Fetches the vnode_id for a given vnode.
@param ctx The VFS context to use.
@param vp The Vnode to get the ID for
@return santa_vnode_id_t The Vnode ID.
*/
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx,
const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, ctx);
return {.fsid = vap.va_fsid, .fileid = vap.va_fileid};
}
/**
Vnode Callback
@param cred The kauth credential for this request.
@param ctx The VFS context for this request.
@param vp The Vnode for this request.
@param errno A pointer to return an errno style error.
@return int A valid KAUTH_RESULT_*.
*/
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
const vnode_t vp, int *errno);
/**
FileOp Callback
@param action The performed action
@param vp The Vnode for this request. May be nullptr.
@param path The path being operated on.
@param new_path The target path for moves and links.
*/
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path,
const char *new_path);
private:
/**
While waiting for a response from the daemon, this is the maximum number of
milliseconds to sleep for before checking the cache for a response.
*/
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
/**
While waiting for a response from the daemon, this is the maximum number
cache checks before re-sending the request.
*/
static const uint32_t kRequestCacheChecks = 5;
/**
The maximum number of milliseconds a cached deny message should be
considered valid.
*/
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
/// Maximum number of entries in the in-kernel cache.
static const uint32_t kMaxCacheSize = 10000;
/// Maximum number of PostToDecisionQueue failures to allow.
static const uint32_t kMaxDecisionQueueFailures = 10;
/**
The maximum number of messages that can be kept in the decision data queue
at any time.
*/
static const uint32_t kMaxDecisionQueueEvents = 512;
/**
The maximum number of messages that can be kept in the logging data queue
at any time.
*/
static const uint32_t kMaxLogQueueEvents = 2048;
/// How long pid monitor thread should sleep between termination checks.
static const uint32_t kPidMonitorSleepTimeMilliseconds = 1000;
/**
When set to true, Santa will check all ancestors of a process to determine
if it is a compiler.
TODO(nguyenphillip): this setting (and others above) should be configurable.
*/
static const bool kCheckCompilerAncestors = false;
/**
Fetches a response from the daemon. Handles both daemon death
and failure to post messages to the daemon.
@param message The message to send to the daemon
@param identifier The vnode ID string for this request
@return santa_action_t The response for this request
*/
santa_action_t GetFromDaemon(santa_message_t *message,
santa_vnode_id_t identifier);
/**
Fetches an execution decision for a file, first using the cache and then
by sending a message to the daemon and waiting until a response arrives.
If a daemon isn't connected, will allow execution and cache, logging
the path to the executed file.
@param cred The credential for this request.
@param vp The Vnode for this request.
@param vnode_id The ID for this vnode.
@return santa_action_t The response for this request
*/
santa_action_t FetchDecision(const kauth_cred_t cred, const vnode_t vp,
const santa_vnode_id_t vnode_id);
/**
Posts the requested message to the decision data queue.
@param message The message to send
@return bool true if sending was successful.
*/
bool PostToDecisionQueue(santa_message_t *message);
/**
Posts the requested message to the logging data queue.
@param message The message to send
@return bool true if sending was successful.
*/
bool PostToLogQueue(santa_message_t *message);
/**
Creates a new santa_message_t with some fields pre-filled.
@param credential The kauth_cred_t for this action, if available.
If nullptr, will get the credential for the current process.
*/
static inline santa_message_t *NewMessage(kauth_cred_t credential) {
bool should_release = false;
if (credential == nullptr) {
credential = kauth_cred_get_with_ref();
should_release = true;
}
auto message = new santa_message_t;
message->uid = kauth_cred_getuid(credential);
message->gid = kauth_cred_getgid(credential);
message->pid = proc_selfpid();
message->ppid = proc_selfppid();
if (should_release) {
kauth_cred_unref(&credential);
}
return message;
}
/**
Returns the current system uptime in microseconds
*/
static inline uint64_t GetCurrentUptime() {
clock_sec_t sec;
clock_usec_t usec;
clock_get_system_microtime(&sec, &usec);
return (uint64_t)((sec * 1000000) + usec);
}
SantaCache<santa_vnode_id_t, uint64_t> *root_decision_cache_;
SantaCache<santa_vnode_id_t, uint64_t> *non_root_decision_cache_;
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;
SantaCache<pid_t, pid_t> *compiler_pid_set_;
SNTPrefixTree *filemod_prefix_filter_;
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<santa_vnode_id_t, uint64_t> *CacheForIdentifier(
const santa_vnode_id_t identifier);
// This is the file system ID of the root filesystem,
// used to determine which cache to use for requests
uint64_t root_fsid_;
lck_grp_t *sdm_lock_grp_;
lck_grp_attr_t *sdm_lock_grp_attr_;
lck_attr_t *sdm_lock_attr_;
lck_mtx_t *decision_dataqueue_lock_;
lck_mtx_t *log_dataqueue_lock_;
IOSharedDataQueue *decision_dataqueue_;
IOSharedDataQueue *log_dataqueue_;
uint32_t failed_decision_queue_requests_;
uint32_t failed_log_queue_requests_;
int32_t listener_invocations_;
int32_t pid_monitor_thread_count_ = 0;
pid_t client_pid_;
kauth_listener_t vnode_listener_;
kauth_listener_t fileop_listener_;
struct timespec ts_ = {
.tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000};
};
/**
The kauth callback function for the Vnode scope
@param credential actor's credentials
@param idata data that was passed when the listener was registered
@param action action that was requested
@param arg0 VFS context
@param arg1 Vnode being operated on
@param arg2 Parent Vnode. May be nullptr.
@param arg3 Pointer to an errno-style error.
*/
extern "C" int vnode_scope_callback(kauth_cred_t credential, void *idata,
kauth_action_t action, uintptr_t arg0,
uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3);
/**
The kauth callback function for the FileOp scope
@param credential actor's credentials
@param idata data that was passed when the listener was registered
@param action action that was requested
@param arg0 depends on action, usually the vnode ref.
@param arg1 depends on action.
@param arg2 depends on action, usually 0.
@param arg3 depends on action, usually 0.
*/
extern "C" int fileop_scope_callback(kauth_cred_t credential, void *idata,
kauth_action_t action, uintptr_t arg0,
uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3);
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H

View File

@@ -1,55 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santa_driver/SantaDriver.h"
#define super IOService
#define SantaDriver com_google_SantaDriver
// The defines above can'be used in this function, we must use the full names.
OSDefineMetaClassAndStructors(com_google_SantaDriver, IOService);
bool SantaDriver::start(IOService *provider) {
if (!super::start(provider)) return false;
santaDecisionManager = new SantaDecisionManager;
if (!santaDecisionManager->init() ||
santaDecisionManager->StartListener() != kIOReturnSuccess) {
santaDecisionManager->release();
santaDecisionManager = nullptr;
return false;
}
registerService();
LOGI("Loaded, version %s.", OSKextGetCurrentVersionString());
return true;
}
void SantaDriver::stop(IOService *provider) {
santaDecisionManager->StopListener();
santaDecisionManager->release();
santaDecisionManager = nullptr;
LOGI("Unloaded.");
super::stop(provider);
}
SantaDecisionManager *SantaDriver::GetDecisionManager() const {
return santaDecisionManager;
}
#undef super

View File

@@ -1,46 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTA_DRIVER__SANTADRIVER_H
#define SANTA__SANTA_DRIVER__SANTADRIVER_H
#include <IOKit/IOService.h>
#include <libkern/OSKextLib.h>
#include "Source/common/SNTLogging.h"
#include "Source/santa_driver/SantaDecisionManager.h"
///
/// The driver class, which provides the start/stop functions and holds
/// the SantaDecisionManager instance which the connected client
/// communicates with.
///
class com_google_SantaDriver : public IOService {
OSDeclareDefaultStructors(com_google_SantaDriver);
public:
/// Called by the kernel when the kext is loaded
bool start(IOService *provider) override;
/// Called by the kernel when the kext is unloaded
void stop(IOService *provider) override;
/// Returns a pointer to the SantaDecisionManager created in start().
SantaDecisionManager *GetDecisionManager() const;
private:
SantaDecisionManager *santaDecisionManager;
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVER_H

View File

@@ -1,305 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "Source/santa_driver/SantaDriverClient.h"
#define super IOUserClient
#define SantaDriverClient com_google_SantaDriverClient
// The defines above can'be used in this function, must use the full names.
OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient);
#pragma mark Driver Management
bool SantaDriverClient::initWithTask(
task_t owningTask, void *securityID, UInt32 type) {
if (clientHasPrivilege(
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
LOGW("Unprivileged client attempted to connect.");
return false;
}
if (!super::initWithTask(owningTask, securityID, type)) return false;
return true;
}
bool SantaDriverClient::start(IOService *provider) {
myProvider = OSDynamicCast(com_google_SantaDriver, provider);
if (!myProvider) return false;
decisionManager = myProvider->GetDecisionManager();
if (!decisionManager) return false;
decisionManager->retain();
return super::start(provider);
}
void SantaDriverClient::stop(IOService *provider) {
myProvider = nullptr;
decisionManager->release();
decisionManager = nullptr;
super::stop(provider);
}
IOReturn SantaDriverClient::clientDied() {
LOGI("Client died.");
decisionManager->DisconnectClient(true);
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
}
IOReturn SantaDriverClient::clientClose() {
LOGI("Client disconnected.");
decisionManager->DisconnectClient();
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
}
bool SantaDriverClient::didTerminate(IOService *provider, IOOptionBits options, bool *defer) {
decisionManager->DisconnectClient(false, 0);
if (myProvider && myProvider->isOpen(this)) myProvider->close(this);
return super::didTerminate(provider, options, defer);
}
#pragma mark Fetching memory and data queue notifications
IOReturn SantaDriverClient::registerNotificationPort(
mach_port_t port, UInt32 type, UInt32 ref) {
if (port == MACH_PORT_NULL) return kIOReturnError;
switch (type) {
case QUEUETYPE_DECISION:
decisionManager->SetDecisionPort(port);
break;
case QUEUETYPE_LOG:
decisionManager->SetLogPort(port);
break;
default:
return kIOReturnBadArgument;
}
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clientMemoryForType(
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) {
switch (type) {
case QUEUETYPE_DECISION:
*options = 0;
*memory = decisionManager->GetDecisionMemoryDescriptor();
decisionManager->ConnectClient(proc_selfpid());
break;
case QUEUETYPE_LOG:
*options = 0;
*memory = decisionManager->GetLogMemoryDescriptor();
break;
default:
return kIOReturnBadArgument;
}
(*memory)->retain();
return kIOReturnSuccess;
}
#pragma mark Callable Methods
IOReturn SantaDriverClient::open(
OSObject *target,
void *reference,
IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (me->isInactive()) return kIOReturnNotAttached;
if (!me->myProvider->open(me)) {
LOGW("A second client tried to connect.");
return kIOReturnExclusiveAccess;
}
LOGI("Client connected.");
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::allow_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::allow_compiler(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW_COMPILER);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_DENY);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ACK);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const bool non_root_only = static_cast<const bool>(arguments->scalarInput[0]);
me->decisionManager->ClearCache(non_root_only);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::remove_cache_entry(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->RemoveFromCache(*vnode_id);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::cache_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
arguments->scalarOutput[0] = me->decisionManager->RootCacheCount();
arguments->scalarOutput[1] = me->decisionManager->NonRootCacheCount();
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::check_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(*vnode_id);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::cache_bucket_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
santa_bucket_count_t *counts = reinterpret_cast<santa_bucket_count_t *>(
arguments->structureOutput);
const santa_bucket_count_t *input = reinterpret_cast<const santa_bucket_count_t *>(
arguments->structureInput);
uint16_t s = sizeof(counts->per_bucket) / sizeof(uint16_t);
counts->start = input->start;
me->decisionManager->CacheBucketCount(counts->per_bucket, &s, &(counts->start));
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::filemod_prefix_filter_add(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const char *prefix = reinterpret_cast<const char *>(arguments->structureInput);
return me->decisionManager->FilemodPrefixFilterAdd(prefix, arguments->scalarOutput);
}
IOReturn SantaDriverClient::filemod_prefix_filter_reset(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
me->decisionManager->FilemodPrefixFilterReset();
return kIOReturnSuccess;
}
#pragma mark Method Resolution
IOReturn SantaDriverClient::externalMethod(
UInt32 selector,
IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch,
OSObject *target,
void *reference) {
/// Array of methods callable by clients. The order of these must match the
/// order of the items in SantaDriverMethods in SNTKernelCommon.h
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::allow_compiler, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::deny_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::acknowledge_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
{ &SantaDriverClient::remove_cache_entry, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
{ &SantaDriverClient::check_cache, 0, sizeof(santa_vnode_id_t), 1, 0 },
{ &SantaDriverClient::cache_bucket_count, 0, sizeof(santa_bucket_count_t),
0, sizeof(santa_bucket_count_t) },
{ &SantaDriverClient::filemod_prefix_filter_add, 0, sizeof(const char[MAXPATHLEN]), 1, 0 },
{ &SantaDriverClient::filemod_prefix_filter_reset, 0, 0, 0, 0 },
};
if (selector >= static_cast<UInt32>(kSantaUserClientNMethods)) {
return kIOReturnBadArgument;
}
dispatch = &(sMethods[selector]);
if (!target) target = this;
return super::externalMethod(selector, arguments, dispatch, target, reference);
}
#undef super

View File

@@ -1,136 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
#define SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
#include <IOKit/IOUserClient.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "Source/common/SNTKernelCommon.h"
#include "Source/santa_driver/SantaDecisionManager.h"
#include "Source/santa_driver/SantaDriver.h"
///
/// This class is instantiated by IOKit when a new client process attempts to
/// connect to the driver. Starting, stopping, handling connections, allocating
/// shared memory and establishing a data queue is handled here.
///
/// Documentation on how the IOUserClient parts of this code work can be found
/// here:
/// https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html
/// https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingDeviceDriver/WritingDeviceDriver.pdf
///
class com_google_SantaDriverClient : public IOUserClient {
OSDeclareDefaultStructors(com_google_SantaDriverClient);
public:
/// Called as part of IOServiceOpen in clients
bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;
/// Called after initWithTask as part of IOServiceOpen
bool start(IOService *provider) override;
/// Called when this class is stopping
void stop(IOService *provider) override;
/// Called when a client manually disconnects (via IOServiceClose)
IOReturn clientClose() override;
/// Called when a client dies
IOReturn clientDied() override;
/// Called during termination
bool didTerminate(IOService *provider, IOOptionBits options,
bool *defer) override;
/// Called in clients with IOConnectSetNotificationPort
IOReturn registerNotificationPort(mach_port_t port, UInt32 type,
UInt32 refCon) override;
/// Called in clients with IOConnectMapMemory
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options,
IOMemoryDescriptor **memory) override;
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
/// to the requested selector using the SantaDriverMethods enum in
/// SNTKernelCommon.
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch, OSObject *target,
void *reference) override;
///
/// The userspace callable methods are below. Each method corresponds
/// to an entry in SantaDriverMethods.
///
/// Called during client connection.
static IOReturn open(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a binary.
static IOReturn allow_binary(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a compiler binary.
static IOReturn allow_compiler(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to deny a binary.
static IOReturn deny_binary(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to acknowledge a binary request. This is used for
/// large binaries that may take a while to reach a decision.
static IOReturn acknowledge_binary(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to empty the cache.
static IOReturn clear_cache(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon call this to remove a single cache entry.
static IOReturn remove_cache_entry(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in the cache
static IOReturn cache_count(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out the status of a vnode_id in the cache.
/// Output will be a santa_action_t.
static IOReturn check_cache(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in each cache
/// bucket. Input and output are both an instance of santa_bucket_count_t.
static IOReturn cache_bucket_count(OSObject *target, void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to add filemod prefix filters at daemon startup.
static IOReturn filemod_prefix_filter_add(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to reset the prefix filter tree.
static IOReturn filemod_prefix_filter_reset(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
private:
com_google_SantaDriver *myProvider;
SantaDecisionManager *decisionManager;
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H

View File

@@ -1,878 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <IOKit/IODataQueueClient.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/kext/KextManager.h>
#import <CommonCrypto/CommonDigest.h>
#include <libkern/OSKextLib.h>
#include <mach/mach.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <cmath>
#include <ctime>
#include <iostream>
#include <numeric>
#include <vector>
#include "Source/common/SNTKernelCommon.h"
///
/// Kernel Extension Tests
///
/// Build and launch as root. This target is dependent on the santa-driver target and these
/// tests will load santa-driver from the same location this binary is executed from, unloading
/// any existing driver (and daemon) if necessary.
///
#define TSTART(testName) \
do { \
printf(" %-50s ", testName); \
} while (0)
#define TPASS() \
do { \
printf("PASS\n"); \
} while (0)
#define TPASSINFO(fmt, ...) \
do { \
printf("PASS\n " fmt "\n", ##__VA_ARGS__); \
} while (0)
#define TFAIL() \
do { \
printf("FAIL\n"); \
[self unloadExtension]; \
exit(1); \
} while (0)
#define TFAILINFO(fmt, ...) \
do { \
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
[self unloadExtension]; \
exit(1); \
} while (0)
@interface SantaKernelTests : NSObject
@property io_connect_t connection;
// A block that tests can set to handle specific files/binaries.
// The block should return an action to respond to the kernel with.
// If no block is specified or no action is returned, the exec will be allowed.
@property(atomic, copy) santa_action_t (^handlerBlock)(santa_message_t msg);
- (void)unloadDaemon;
- (void)unloadExtension;
- (void)loadExtension;
- (void)runTests;
@end
@implementation SantaKernelTests
#pragma mark - Test Helpers
/// Return an initialized NSTask for |path| with stdout, stdin and stderr directed to /dev/null
- (NSTask *)taskWithPath:(NSString *)path {
NSTask *t = [[NSTask alloc] init];
t.launchPath = path;
t.standardInput = nil;
t.standardOutput = nil;
t.standardError = nil;
return t;
}
- (NSString *)sha256ForPath:(NSString *)path {
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
NSData *fData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:nil];
CC_SHA256([fData bytes], (unsigned int)[fData length], sha256);
char buf[CC_SHA256_DIGEST_LENGTH * 2 + 1];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
snprintf(buf + (2 * i), 4, "%02x", (unsigned char)sha256[i]);
}
buf[CC_SHA256_DIGEST_LENGTH * 2] = '\0';
return @(buf);
}
/// Return the path to the version of ld being used by clang.
- (NSString *)ldPath {
static NSString *path;
if (!path) {
NSTask *xcrun = [self taskWithPath:@"/usr/bin/xcrun"];
xcrun.arguments = @[ @"-f", @"ld" ];
xcrun.standardOutput = [NSPipe pipe];
@try {
[xcrun launch];
[xcrun waitUntilExit];
} @catch (NSException *exception) {
return nil;
}
if (xcrun.terminationStatus != 0) return nil;
NSData *data = [[xcrun.standardOutput fileHandleForReading] readDataToEndOfFile];
if (!data) return nil;
path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
return path;
}
#pragma mark - Driver Helpers
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
/// passing the |vnodeID|.
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeid {
switch (action) {
case ACTION_RESPOND_ALLOW:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_DENY:
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ACK:
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ALLOW_COMPILER:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler, &vnodeid,
sizeof(vnodeid), 0, 0);
break;
default:
TFAILINFO("postToKernelAction:forVnodeID: received unknown action type: %d", action);
break;
}
}
/// Call in-kernel function: |kSantaUserClientClearCache|
- (void)flushCache {
uint64_t nonRootOnly = 0;
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, &nonRootOnly, 1, 0, 0);
}
#pragma mark - Connection Tests
/// Tests the process of locating, attaching and opening the driver. Also verifies that the
/// driver correctly refuses non-privileged connections.
- (void)connectionTests {
kern_return_t kr;
io_service_t serviceObject;
CFDictionaryRef classToMatch;
TSTART("Creates matching service dictionary");
if (!(classToMatch = IOServiceMatching(USERCLIENT_CLASS))) {
TFAIL();
}
TPASS();
TSTART("Locates Santa driver");
serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch);
if (!serviceObject) {
TFAILINFO("Is santa-driver.kext loaded?");
}
TPASS();
TSTART("Driver refuses non-privileged connections");
(void)setegid(-2);
(void)seteuid(-2);
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
if (kr != kIOReturnBadArgument) {
TFAIL();
}
(void)setegid(0);
(void)seteuid(0);
TPASS();
TSTART("Attaches to and starts Santa service");
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
IOObjectRelease(serviceObject);
if (kr != kIOReturnSuccess) {
TFAILINFO("KR: %d", kr);
}
TPASS();
TSTART("Calls 'open' method on driver");
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr == kIOReturnExclusiveAccess) {
TFAILINFO("A client is already connected to the driver.\n"
"Please kill the existing client and re-run the test.");
} else if (kr != kIOReturnSuccess) {
TFAILINFO("KR: %d", kr);
}
TPASS();
TSTART("Refuses second client");
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr != kIOReturnExclusiveAccess) {
TFAIL();
}
TPASS();
}
#pragma mark - Listener
/// Tests the process of allocating & registering a notification port and mapping shared memory.
/// From then on, monitors the IODataQueue and responds for files specifically used in other tests.
/// For everything else, allows execution normally to avoid deadlocking the system.
- (void)beginListening {
TSTART("Allocates a notification port");
mach_port_t receivePort;
if (!(receivePort = IODataQueueAllocateNotificationPort())) {
TFAIL();
}
TPASS();
TSTART("Registers the notification port");
kern_return_t kr =
IOConnectSetNotificationPort(self.connection, QUEUETYPE_DECISION, receivePort, 0);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), receivePort);
TFAILINFO("KR: %d", kr);
return;
}
TPASS();
TSTART("Maps shared memory");
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(), &address, &size,
kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), receivePort);
TFAILINFO("KR: %d", kr);
}
TPASS();
/// Begin listening for events
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
do {
while (IODataQueueDataAvailable(queueMemory)) {
santa_message_t vdata;
UInt32 dataSize = sizeof(vdata);
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
if (kr != kIOReturnSuccess) {
TFAILINFO("Error receiving data: %d", kr);
continue;
}
if (vdata.action != ACTION_REQUEST_BINARY) continue;
santa_action_t action = ACTION_RESPOND_ALLOW;
@synchronized(self) {
if (self.handlerBlock) action = self.handlerBlock(vdata);
}
[self postToKernelAction:action forVnodeID:vdata.vnode_id];
}
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
IOConnectUnmapMemory(self.connection, kIODefaultMemoryType, mach_task_self(), address);
mach_port_destroy(mach_task_self(), receivePort);
}
#pragma mark - Functional Tests
- (void)receiveAndBlockTests {
TSTART("Blocks denied binaries");
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/ed", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
return ACTION_RESPOND_ALLOW;
};
NSTask *ed = [self taskWithPath:@"/bin/ed"];
@try {
[ed launch];
[ed waitUntilExit];
TFAIL();
} @catch (NSException *exception) {
TPASS();
}
}
- (void)receiveAndCacheTests {
TSTART("Permits & caches allowed binaries");
__block int timesSeenLs = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/ls", msg.path, 7) == 0) ++timesSeenLs;
return ACTION_RESPOND_ALLOW;
};
NSTask *ls = [self taskWithPath:@"/bin/ls"];
[ls launch];
[ls waitUntilExit];
if (timesSeenLs != 1) {
TFAILINFO("Didn't record first run of ls");
}
ls = [self taskWithPath:@"/bin/ls"];
[ls launch];
[ls waitUntilExit];
if (timesSeenLs > 1) {
TFAILINFO("Received request for ls a second time");
}
TPASS();
}
- (void)invalidatesCacheTests {
TSTART("Invalidates cache for manually closed FDs");
NSFileManager *fm = [NSFileManager defaultManager];
NSString *target =
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
[fm removeItemAtPath:target error:nil];
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Copy the pwd binary to a new file
if (![fm copyItemAtPath:@"/bin/pwd" toPath:target error:nil]) {
TFAILINFO("Failed to create temp file");
}
// Launch the new file to put it in the cache
NSTask *pwd = [self taskWithPath:target];
[pwd launch];
[pwd waitUntilExit];
// Exit if this fails with a useful message.
if ([pwd terminationStatus] != 0) {
TFAILINFO("First launch of test binary failed");
}
// Now replace the contents of the test file (which is cached) with the contents of /bin/ed,
// which is blocked by SHA-256 during the tests.
FILE *infile = fopen("/bin/ed", "r");
FILE *outfile = fopen(target.UTF8String, "w");
int ch;
while ((ch = fgetc(infile)) != EOF) {
fputc(ch, outfile);
}
fclose(infile);
// Now try running the temp file again. If it succeeds, the test failed.
NSTask *ed = [self taskWithPath:target];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after write while file open");
[fm removeItemAtPath:target error:nil];
} @catch (NSException *exception) {
// This is a pass, but we have more to do.
}
// Close the file to flush the write.
fclose(outfile);
// And try running the temp file again. If it succeeds, the test failed.
ed = [self taskWithPath:target];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after file closed");
} @catch (NSException *exception) {
TPASS();
} @finally {
[fm removeItemAtPath:target error:nil];
}
}
- (void)invalidatesCacheAutoCloseTest {
TSTART("Invalidates cache for auto closed FDs");
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Create temporary file
NSFileManager *fm = [NSFileManager defaultManager];
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
TFAILINFO("Failed to create temp file");
}
// Launch the new file to put it in the cache
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
[pwd launch];
[pwd waitUntilExit];
if ([pwd terminationStatus] != 0) {
TFAILINFO("Second launch of test binary failed");
}
// Replace file contents using dd, which doesn't close FDs
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
NSTask *dd = [self taskWithPath:@"/bin/dd"];
dd.arguments = @[
@"if=/bin/ed", @"of=invalidacachetest_tmp", @"bs=1",
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
];
[dd launch];
[dd waitUntilExit];
// And try running the temp file again. If it succeeds, the test failed.
NSTask *ed = [self taskWithPath:@"invalidacachetest_tmp"];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after file closed");
} @catch (NSException *exception) {
TPASS();
} @finally {
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
}
}
- (void)clearCacheTests {
TSTART("Can clear cache");
__block int timesSeenCat = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/cat", msg.path, 8) == 0) ++timesSeenCat;
return ACTION_RESPOND_ALLOW;
};
NSTask *cat = [self taskWithPath:@"/bin/cat"];
[cat launch];
[cat waitUntilExit];
if (timesSeenCat != 1) {
TFAILINFO("Didn't record first run of cat");
}
[self flushCache];
cat = [self taskWithPath:@"/bin/cat"];
[cat launch];
[cat waitUntilExit];
if (timesSeenCat != 2) {
TFAIL();
}
TPASS();
}
- (void)blocksDeniedTracedBinaries {
TSTART("Denies blocked processes running while traced");
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/mv", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
return ACTION_RESPOND_ALLOW;
};
pid_t pid = fork();
if (pid < 0) {
TFAILINFO("Failed to fork");
} else if (pid > 0) {
int status;
while (waitpid(pid, &status, 0) != pid)
; // handle EINTR
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
TPASS();
} else if (WIFSTOPPED(status)) {
TFAILINFO("Process was executed and is waiting for debugger");
} else {
TFAILINFO("Process did not exit with EPERM as expected");
}
} else if (pid == 0) {
fclose(stdout);
fclose(stderr);
ptrace(PT_TRACE_ME, 0, 0, 0);
execl("/bin/mv", "mv", NULL);
_exit(errno);
}
}
- (void)testCachePerformance {
TSTART("Test cache performance...");
// Execute echo 100 times, saving the time taken for each run
std::vector<double> times;
for (int i = 0; i < 100; ++i) {
NSTask *t = [[NSTask alloc] init];
t.launchPath = @"/bin/echo";
t.standardOutput = [NSPipe pipe];
auto start = std::chrono::steady_clock::now();
[t launch];
[t waitUntilExit];
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
if (i > 5) times.push_back(duration);
}
// Sort and remove first 10 and last 10 entries.
std::sort(times.begin(), times.end());
times.erase(times.begin(), times.begin() + 10);
times.erase(times.end() - 10, times.end());
// Calculate mean
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
// Calculate stdev
double accum = 0.0;
std::for_each(times.begin(), times.end(),
[&](const double d) { accum += (d - mean) * (d - mean); });
double stdev = sqrt(accum / (times.size() - 1));
if (mean > 80 || stdev > 10) {
TFAILINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
} else {
TPASSINFO("ms: %-3.2f σ: %-3.2f", mean, stdev);
}
}
- (void)testLargeBinary {
TSTART("Handles large binary...");
__block int calCount = 0;
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if (strncmp("/usr/bin/cal", msg.path, 12) == 0) {
if (calCount++) TFAILINFO("Large binary should not re-request");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
dispatch_get_global_queue(0, 0), ^{
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
});
return ACTION_RESPOND_ACK;
}
return ACTION_RESPOND_ALLOW;
};
@try {
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
[testexec launch];
int sleepCount = 0;
while ([testexec isRunning]) {
sleep(1);
if (++sleepCount > 5) TFAILINFO("Took longer than expected to start/stop");
}
} @catch (NSException *e) {
TFAILINFO("Failed to launch");
}
TPASS();
}
- (void)testPendingTransitiveRules {
TSTART("Adds pending transitive whitelist rules");
NSString *ldPath = [self ldPath];
if (!ldPath) {
TFAILINFO("Couldn't get path to ld");
}
// Clear out cached decisions from any previous tests.
[self flushCache];
__block int ldCount = 0;
__block int helloCount = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (!strcmp(ldPath.UTF8String, msg.path)) {
ldCount++;
return ACTION_RESPOND_ALLOW_COMPILER;
} else if (!strcmp("/private/tmp/hello", msg.path)) {
helloCount++;
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Write source file to /tmp/hello.c
FILE *out = fopen("/tmp/hello.c", "wb");
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
fclose(out);
// Then compile it with clang and ld, the latter of which has been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
[clang launch];
[clang waitUntilExit];
// Make sure that our version of ld marked as compiler was run. This assumes that
// "xcode-select -p" returns "/Applications/Xcode.app/Contents/Developer"
if (ldCount != 1) {
TFAILINFO("Didn't record run of ld");
}
// Check if we can now run /private/tmp/hello. If working correctly, there will already be
// a pending transitive rule in the cache, so no decision request will be sent to listener.
// If for some reason a decision request is sent, then binary will be denied.
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
@try {
[hello launch];
[hello waitUntilExit];
} @catch (NSException *exception) {
TFAILINFO("could not launch /private/tmp/hello: %s", exception.reason.UTF8String);
}
// Check that the listener was not consulted for the decision.
if (helloCount > 0) {
TFAILINFO("pending decision for /private/tmp/hello was not in cache");
}
// Clean up
remove("/tmp/hello");
remove("/tmp/hello.c");
TPASS();
}
- (void)testNoTransitiveRules {
TSTART("No transitive rule generated by non-compiler");
NSString *ldPath = [self ldPath];
if (!ldPath) {
TFAILINFO("Couldn't get path to ld");
}
// Clear out cached decisions from any previous tests.
[self flushCache];
__block int ldCount = 0;
__block int helloCount = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (!strcmp(ldPath.UTF8String, msg.path)) {
ldCount++;
return ACTION_RESPOND_ALLOW;
} else if (!strcmp("/private/tmp/hello", msg.path)) {
helloCount++;
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Write source file to /tmp/hello.c
FILE *out = fopen("/tmp/hello.c", "wb");
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
fclose(out);
// Then compile it with clang and ld, neither of which have been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
@try {
[clang launch];
[clang waitUntilExit];
} @catch (NSException *exception) {
TFAILINFO("Couldn't launch clang");
}
// Make sure that our version of ld was run. This assumes that "xcode-select -p"
// returns "/Applications/Xcode.app/Contents/Developer"
if (ldCount != 1) {
TFAILINFO("Didn't record run of ld");
}
// Check that we cannot run /private/tmp/hello.
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
@try {
[hello launch];
[hello waitUntilExit];
TFAILINFO("Should not have been able to launch /private/tmp/hello");
} @catch (NSException *exception) {
// All good
}
// Check that there wasn't a decision for /private/tmp/hello in the cache.
if (helloCount != 1) {
TFAILINFO("decision for /private/tmp/hello found in cache");
}
// Clean up
remove("/tmp/hello");
remove("/tmp/hello.c");
TPASS();
}
- (void)testFilemodPrefixFilter {
TSTART("Testing filemod prefix filter");
NSString *filter =
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
@"are the same for all non-accelerating observers, and he showed that the speed of light "
@"within a vacuum is the same no matter the speed at which an observer travels.";
// Create a buffer that has 1024 bytes of characters, non-terminating.
char buffer[MAXPATHLEN + 1]; // +1 is for the null byte from strlcpy(). It falls off the ledge.
for (int i = 0; i < 4; ++i) {
if (![filter getFileSystemRepresentation:buffer + (i * filter.length) maxLength:MAXPATHLEN]) {
TFAILINFO("Invalid filemod prefix filter: %s", filter.UTF8String);
}
}
strlcpy(&buffer[1016], "E = mc²", 9); // Fill in the last 8 bytes.
uint64_t n = 0;
uint32_t n_len = 1;
// The filter should currently be empty. It is reset when the client disconnects.
// Fill up the 1024 node capacity.
kern_return_t ret =
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess || n != 1024) {
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
}
// Make sure it will enforce capacity.
const char *too_much = "B";
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
too_much, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnNoResources || n != 1024) {
TFAILINFO("Failed enforce capacity: got %llu nodes expected 1024", n);
}
// Make sure it will prune.
const char *ignore_it_all = "A";
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
ignore_it_all, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
// Expect 1 "A" node.
if (ret != kIOReturnSuccess || n != 1) {
TFAILINFO("Failed to prune the prefix filter: got %llu nodes expected 1", n);
}
// Reset.
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
NULL, NULL);
// And fill it back up again.
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
buffer, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess || n != 1024) {
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
}
TPASS();
}
#pragma mark - Main
- (void)unloadDaemon {
NSTask *t = [[NSTask alloc] init];
t.launchPath = @"/bin/launchctl";
t.arguments = @[ @"remove", @"com.google.santad" ];
t.standardOutput = t.standardError = [NSPipe pipe];
[t launch];
[t waitUntilExit];
}
- (void)unloadExtension {
// Don't check the status of this, the kext may not be loaded..
OSStatus ret = KextManagerUnloadKextWithIdentifier(CFSTR("com.google.santa-driver"));
if (ret != kOSReturnSuccess && ret != kOSKextReturnNotFound) {
NSLog(@"Failed to unload extension: 0x%X", ret);
}
}
- (void)loadExtension {
TSTART("Loads extension");
NSError *error;
NSFileManager *fm = [NSFileManager defaultManager];
NSString *src = [[fm currentDirectoryPath] stringByAppendingPathComponent:@"santa-driver.kext"];
NSString *dest = [NSTemporaryDirectory() stringByAppendingPathComponent:@"santa-driver.kext"];
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
if (![fm copyItemAtPath:src toPath:dest error:&error] || error) {
TFAILINFO("Failed to copy kext: %s", error.description.UTF8String);
}
NSDictionary *attrs = @{
NSFileOwnerAccountName : @"root",
NSFileGroupOwnerAccountName : @"wheel",
NSFilePosixPermissions : @0755
};
[fm setAttributes:attrs ofItemAtPath:dest error:NULL];
for (NSString *path in [fm enumeratorAtPath:dest]) {
[fm setAttributes:attrs ofItemAtPath:[dest stringByAppendingPathComponent:path] error:NULL];
}
NSURL *destURL = [NSURL fileURLWithPath:dest];
OSStatus ret = KextManagerLoadKextWithURL((__bridge CFURLRef)destURL, NULL);
if (ret != kOSReturnSuccess) {
TFAILINFO("Failed to load kext: 0x%X", ret);
}
usleep(50000);
TPASS();
}
- (void)runTests {
printf("-> Connection tests:\n");
// Test that connection can be established
[self connectionTests];
// Open driver and begin listening for events. Run this on background thread
// so we can continue running tests.
[self performSelectorInBackground:@selector(beginListening) withObject:nil];
// Wait for driver to finish getting ready
sleep(1);
printf("\n-> Functional tests:\n");
[self receiveAndBlockTests];
[self receiveAndCacheTests];
[self invalidatesCacheTests];
[self invalidatesCacheAutoCloseTest];
[self clearCacheTests];
[self blocksDeniedTracedBinaries];
[self testLargeBinary];
[self testPendingTransitiveRules];
[self testNoTransitiveRules];
[self testFilemodPrefixFilter];
printf("\n-> Performance tests:\n");
[self testCachePerformance];
printf("\nAll tests passed.\n\n");
}
@end
int main(int argc, const char *argv[]) {
@autoreleasepool {
setbuf(stdout, NULL);
if (getuid() != 0) {
printf("Please run as root\n");
exit(1);
}
SantaKernelTests *skt = [[SantaKernelTests alloc] init];
printf("\nSanta Kernel Tests\n==================\n\n");
printf("-> Loading tests:\n");
[skt unloadDaemon];
[skt unloadExtension];
[skt loadExtension];
printf("\n");
[skt runTests];
[skt unloadExtension];
}
return 0;
}

View File

@@ -1,29 +0,0 @@
#include <mach/mach_types.h>
#define STR_EXPAND(tok) #tok
#define STR(tok) STR_EXPAND(tok)
extern "C" {
extern kern_return_t _start(kmod_info_t *ki, void *data);
extern kern_return_t _stop(kmod_info_t *ki, void *data);
__attribute__((visibility("default"))) \
KMOD_EXPLICIT_DECL(com.google.santa-driver, STR(SANTA_VERSION), _start, _stop)
__private_extern__ kmod_start_func_t *_realmain = 0;
__private_extern__ kmod_stop_func_t *_antimain = 0;
__private_extern__ int _kext_apple_cc = __APPLE_CC__ ;
}
#include <IOKit/IOService.h>
#include <IOKit/IOUserClient.h>
// The macOS 10.15 SDK added these Dispatch methods but they aren't
// available on older macOS versions and so prevent kext linking.
// Defining them here as hidden weak no-op's fixes linking and seems to work.
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSMetaClassBase::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) OSObject::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOService::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }
kern_return_t __attribute__((visibility("hidden"))) __attribute__((weak)) IOUserClient::Dispatch(const IORPC rpc) { return KERN_SUCCESS; }

View File

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

View File

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

View File

@@ -13,13 +13,12 @@
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <IOKit/kext/KextManager.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommon.h"
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTFileInfo.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@@ -50,7 +49,6 @@ REGISTER_COMMAND_NAME(@"version")
- (void)runWithArguments:(NSArray *)arguments {
if ([arguments containsObject:@"--json"]) {
NSDictionary *versions = @{
@"santa-driver" : [self santaKextVersion],
@"santad" : [self santadVersion],
@"santactl" : [self santactlVersion],
@"SantaGUI" : [self santaAppVersion],
@@ -62,7 +60,6 @@ REGISTER_COMMAND_NAME(@"version")
encoding:NSUTF8StringEncoding];
printf("%s\n", [versionsStr UTF8String]);
} else {
printf("%-15s | %s\n", "santa-driver", [[self santaKextVersion] UTF8String]);
printf("%-15s | %s\n", "santad", [[self santadVersion] UTF8String]);
printf("%-15s | %s\n", "santactl", [[self santactlVersion] UTF8String]);
printf("%-15s | %s\n", "SantaGUI", [[self santaAppVersion] UTF8String]);
@@ -70,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"];

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,97 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#include "Source/common/SNTKernelCommon.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
@class SNTNotificationMessage;
///
/// Manages the connection between daemon and kernel.
///
@interface SNTDriverManager : NSObject <SNTEventProvider>
///
/// Handles locating and connecting to the driver. If driver is not loaded, will
/// sleep until it is. If driver is loaded but connection fails (like if a client is
/// already connected), will return nil.
///
- (instancetype)init;
///
/// Unloads the driver.
///
+ (void)unloadDriver;
///
/// Handles requests from the kernel using the given block.
/// @note Loops indefinitely unless there is an error trying to read data from the data queue.
///
- (void)listenForDecisionRequests:(void (^)(santa_message_t message))callback;
///
/// Handles requests from the kernel using the given block.
/// @note Loops indefinitely unless there is an error trying to read data from the data queue.
///
- (void)listenForLogRequests:(void (^)(santa_message_t message))callback;
///
/// Sends a response to a query back to the kernel.
///
- (kern_return_t)postAction:(santa_action_t)action forMessage:(santa_message_t)sm;
///
/// Get the number of binaries in the kernel's caches.
///
- (NSArray<NSNumber *> *)cacheCounts;
///
/// Return an array representing all buckets in the kernel's decision cache where each number
/// is the number of items in that bucket.
///
- (NSArray<NSNumber *> *)cacheBucketCount;
///
/// Flush the kernel's binary cache.
///
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly;
///
/// Check the kernel cache for a VnodeID.
///
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID;
///
/// Returns whether the connection to the driver has been established.
///
@property(readonly) BOOL connectionEstablished;
///
/// Remove single entry from the kernel cache for given VnodeID.
///
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId;
///
/// Adds in-kernel prefix filters for file modification logs.
///
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters;
///
/// Resets the filter.
///
- (void)fileModificationPrefixFilterReset;
@end

View File

@@ -1,314 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santad/EventProviders/SNTDriverManager.h"
#import <IOKit/IODataQueueClient.h>
#import <IOKit/kext/KextManager.h>
#include <mach/mach_port.h>
#import "Source/common/SNTLogging.h"
@interface SNTDriverManager ()
@property io_connect_t connection;
@property(readwrite) BOOL connectionEstablished;
@property(nonatomic, readonly) dispatch_queue_t dmAuthQueue;
@property(nonatomic, readonly) dispatch_queue_t dmNotifiyQueue;
@end
@implementation SNTDriverManager
#pragma mark init/dealloc
- (instancetype)init {
self = [super init];
if (self) {
_dmAuthQueue =
dispatch_queue_create("com.google.santa.daemon.dm_auth", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(_dmAuthQueue,
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0));
_dmNotifiyQueue =
dispatch_queue_create("com.google.santa.daemon.dm_notify", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(_dmNotifiyQueue, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
CFDictionaryRef classToMatch = IOServiceMatching(USERCLIENT_CLASS);
if (!classToMatch) {
LOGE(@"Failed to create matching dictionary");
return nil;
}
// Attempt to load driver. It may already be running, so ignore any return value.
KextManagerLoadKextWithIdentifier(CFSTR(USERCLIENT_ID), NULL);
// Wait for the driver to appear
[self waitForDriver:classToMatch];
}
return self;
}
- (void)dealloc {
IOServiceClose(_connection);
}
+ (void)unloadDriver {
KextManagerUnloadKextWithIdentifier(CFSTR(USERCLIENT_ID));
}
#pragma mark Driver Waiting
// Helper function used with IOServiceAddMatchingNotification which expects
// a DriverAppearedBlock to be passed as the 'reference' argument. The block
// will be called for each object iterated over and if the block wants to keep
// the object, it should call IOObjectRetain().
typedef void (^DriverAppearedBlock)(io_object_t object);
static void driverAppearedHandler(void *info, io_iterator_t iterator) {
DriverAppearedBlock block = (__bridge DriverAppearedBlock)info;
if (!block) return;
io_object_t object = 0;
while ((object = IOIteratorNext(iterator))) {
block(object);
IOObjectRelease(object);
}
}
// Wait for the driver to appear, then attach to and open it.
- (void)waitForDriver:(CFDictionaryRef CF_RELEASES_ARGUMENT)matchingDict {
IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop],
IONotificationPortGetRunLoopSource(notificationPort), kCFRunLoopDefaultMode);
io_iterator_t iterator = 0;
DriverAppearedBlock block = ^(io_object_t object) {
// This calls `initWithTask`, `attach` and `start` in `SantaDriverClient`
kern_return_t kr = IOServiceOpen(object, mach_task_self(), 0, &self->_connection);
if (kr != kIOReturnSuccess) {
LOGE(@"Failed to open santa-driver service: 0x%X", kr);
exit(1);
}
// Call `open` in `SantaDriverClient`
kr = IOConnectCallMethod(self->_connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr == kIOReturnExclusiveAccess) {
LOGE(@"A client is already connected");
exit(2);
} else if (kr != kIOReturnSuccess) {
LOGE(@"An error occurred while opening the connection: 0x%X", kr);
exit(3);
}
// Release the iterator to disarm the notifications.
IOObjectRelease(iterator);
IONotificationPortDestroy(notificationPort);
self->_connectionEstablished = YES;
};
LOGI(@"Waiting for Santa driver to become available");
IOServiceAddMatchingNotification(notificationPort, kIOMatchedNotification, matchingDict,
driverAppearedHandler, (__bridge_retained void *)block,
&iterator);
// Call the handler once to 'empty' the iterator, arming it. If the driver is already loaded
// this will immediately cause the connection to be fully established.
driverAppearedHandler((__bridge_retained void *)block, iterator);
}
#pragma mark Incoming messages
- (void)listenForDecisionRequests:(void (^)(santa_message_t))callback {
[self listenForRequestsOfType:QUEUETYPE_DECISION withCallback:callback];
}
- (void)listenForLogRequests:(void (^)(santa_message_t))callback {
[self listenForRequestsOfType:QUEUETYPE_LOG withCallback:callback];
}
- (void)listenForRequestsOfType:(santa_queuetype_t)type
withCallback:(void (^)(santa_message_t))callback {
while (!self.connectionEstablished)
usleep(100000); // 100ms
// Allocate a mach port to receive notifactions from the IODataQueue
mach_port_t receivePort = IODataQueueAllocateNotificationPort();
if (receivePort == MACH_PORT_NULL) {
LOGD(@"Failed to allocate notification port");
return;
}
// This will call registerNotificationPort() inside our user client class
kern_return_t kr = IOConnectSetNotificationPort(self.connection, type, receivePort, 0);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to register notification port for type %d: 0x%X", type, kr);
mach_port_destroy(mach_task_self(), receivePort);
return;
}
// This will call clientMemoryForType() inside our user client class.
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
kr = IOConnectMapMemory(self.connection, type, mach_task_self(), &address, &size, kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to map memory for type %d: 0x%X", type, kr);
mach_port_destroy(mach_task_self(), receivePort);
return;
}
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
do {
while (IODataQueueDataAvailable(queueMemory)) {
santa_message_t vdata;
uint32_t dataSize = sizeof(vdata);
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
if (kr == kIOReturnSuccess) {
switch (type) {
case QUEUETYPE_DECISION: {
dispatch_async(self.dmAuthQueue, ^{
callback(vdata);
});
break;
}
case QUEUETYPE_LOG: {
dispatch_async(self.dmNotifiyQueue, ^{
callback(vdata);
});
break;
}
default: {
break;
}
}
} else {
LOGE(@"Error dequeuing data for type %d: 0x%X", type, kr);
exit(2);
}
}
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
IOConnectUnmapMemory(self.connection, type, mach_task_self(), address);
mach_port_destroy(mach_task_self(), receivePort);
}
#pragma mark Outgoing messages
- (kern_return_t)postAction:(santa_action_t)action forMessage:(santa_message_t)sm {
santa_vnode_id_t vnodeId = sm.vnode_id;
switch (action) {
case ACTION_RESPOND_ALLOW:
return IOConnectCallStructMethod(_connection, kSantaUserClientAllowBinary, &vnodeId,
sizeof(vnodeId), 0, 0);
case ACTION_RESPOND_DENY:
return IOConnectCallStructMethod(_connection, kSantaUserClientDenyBinary, &vnodeId,
sizeof(vnodeId), 0, 0);
case ACTION_RESPOND_ACK:
return IOConnectCallStructMethod(_connection, kSantaUserClientAcknowledgeBinary, &vnodeId,
sizeof(vnodeId), 0, 0);
case ACTION_RESPOND_ALLOW_COMPILER:
return IOConnectCallStructMethod(_connection, kSantaUserClientAllowCompiler, &vnodeId,
sizeof(vnodeId), 0, 0);
default: return KERN_INVALID_ARGUMENT;
}
}
- (NSArray<NSNumber *> *)cacheCounts {
uint32_t input_count = 2;
uint64_t cache_counts[2] = {0, 0};
IOConnectCallScalarMethod(_connection, kSantaUserClientCacheCount, 0, 0, cache_counts,
&input_count);
return @[ @(cache_counts[0]), @(cache_counts[1]) ];
}
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly {
const uint64_t nonRoot = nonRootOnly;
return IOConnectCallScalarMethod(_connection, kSantaUserClientClearCache, &nonRoot, 1, 0, 0) ==
KERN_SUCCESS;
}
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId {
return IOConnectCallStructMethod(_connection, kSantaUserClientRemoveCacheEntry, &vnodeId,
sizeof(vnodeId), 0, 0);
}
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
uint64_t output;
uint32_t outputCnt = 1;
IOConnectCallMethod(self.connection, kSantaUserClientCheckCache, NULL, 0, &vnodeID,
sizeof(santa_vnode_id_t), &output, &outputCnt, NULL, 0);
return (santa_action_t)output;
}
- (NSArray *)cacheBucketCount {
santa_bucket_count_t counts = {};
size_t size = sizeof(counts);
NSMutableArray *a = [NSMutableArray array];
do {
IOConnectCallStructMethod(self.connection, kSantaUserClientCacheBucketCount, &counts, size,
&counts, &size);
for (uint64_t i = 0; i < sizeof(counts.per_bucket) / sizeof(uint16_t); ++i) {
[a addObject:@(counts.per_bucket[i])];
}
} while (counts.start > 0);
return a;
}
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters {
while (!self.connectionEstablished)
usleep(100000); // 100ms
uint64_t n = 0;
uint32_t n_len = 1;
for (NSString *filter in filters) {
char buffer[MAXPATHLEN];
if (![filter getFileSystemRepresentation:buffer maxLength:MAXPATHLEN]) {
LOGE(@"Invalid filemod prefix filter: %@", filter);
continue;
}
kern_return_t ret =
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess) {
LOGE(@"Failed to add prefix filter: %s error: 0x%x", buffer, ret);
// If the tree is full, stop.
// TODO(bur): Maybe continue here with some smarts around the size? Shorter, not yet iterated
// prefixes (that may fit) are ignored. Seems worth it not to spam the driver.
if (ret == kIOReturnNoResources) {
LOGE(@"Prefix filter tree is full!");
return;
}
} else {
LOGI(@"Added prefix filter: %s", buffer);
}
}
}
- (void)fileModificationPrefixFilterReset {
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
NULL, NULL);
}
@end

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,8 @@
<string>Google, LLC.</string>
<key>NSSystemExtensionUsageDescription</key>
<string>Santa knows who is naughty and nice.</string>
<key>NSEndpointSecurityEarlyBoot</key>
<true/>
<key>CFBundleExecutable</key>
<string>com.google.santa.daemon</string>
</dict>

View File

@@ -14,10 +14,11 @@
#import <Foundation/Foundation.h>
#include "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTCommon.h"
@class SNTCachedDecision;
@class SNTStoredEvent;
@class SNTAllowlistInfo;
///
/// Abstract interface for logging execution and file write events to syslog
@@ -37,7 +38,8 @@
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events;
- (void)logFork:(santa_message_t)message;
- (void)logExit:(santa_message_t)message;
- (void)writeLog:(NSString *)log;
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo;
- (void)forceFlush;
// Methods for storing, retrieving, and removing cached decisions.
- (void)cacheDecision:(SNTCachedDecision *)cd;
@@ -54,7 +56,7 @@
- (NSString *)nameForGID:(gid_t)gid;
- (NSString *)sanitizeString:(NSString *)inStr;
- (NSString *)serialForDevice:(NSString *)devPath;
- (NSString *)originalPathForTranslocation:(santa_message_t)message;
- (NSString *)originalPathForTranslocation:(const santa_message_t *)message;
// A cache for usernames and groups.
@property(readonly, nonatomic) NSCache<NSNumber *, NSString *> *userNameMap;

View File

@@ -25,6 +25,7 @@
#import "Source/santad/SNTDatabaseController.h"
#import "Source/santad/Logs/SNTFileEventLog.h"
#import "Source/santad/Logs/SNTProtobufEventLog.h"
#import "Source/santad/Logs/SNTSyslogEventLog.h"
@interface SNTEventLog ()
@@ -92,7 +93,11 @@
[self doesNotRecognizeSelector:_cmd];
}
- (void)writeLog:(NSString *)log {
- (void)logAllowlist:(SNTAllowlistInfo *)allowlistInfo {
[self doesNotRecognizeSelector:_cmd];
}
- (void)forceFlush {
[self doesNotRecognizeSelector:_cmd];
}
@@ -380,7 +385,11 @@
Security.framework so that we can still build against the 10.11 SDK. If the path has not been
translocated or if running on macOS prior to 10.12, this method returns nil.
*/
- (NSString *)originalPathForTranslocation:(santa_message_t)message {
- (NSString *)originalPathForTranslocation:(const santa_message_t *)message {
if (!message) {
return nil;
}
// The first time this function is called, we attempt to find the addresses of
// SecTranslocateIsTranslocatedURL and SecTranslocateCreateOriginalPathForURL inside of the
// Security.framework library. If we were successful, handle will be non-NULL and is never
@@ -405,7 +414,7 @@
if (!IsTranslocatedURL || !CreateOriginalPathForURL) return nil;
// Determine if the executable URL has been translocated or not.
CFURLRef cfExecURL = (__bridge CFURLRef)[NSURL fileURLWithPath:@(message.path)];
CFURLRef cfExecURL = (__bridge CFURLRef)[NSURL fileURLWithPath:@(message->path)];
bool isTranslocated = false;
if (!IsTranslocatedURL(cfExecURL, &isTranslocated, NULL) || !isTranslocated) return nil;
@@ -413,7 +422,7 @@
// launched the executable. So we temporarily drop from root down to this uid, then reset.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
pthread_setugid_np(message.uid, message.gid);
pthread_setugid_np(message->uid, message->gid);
NSURL *origURL = CFBridgingRelease(CreateOriginalPathForURL(cfExecURL, NULL));
pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE);
#pragma clang diagnostic pop
@@ -422,7 +431,7 @@
}
+ (instancetype)logger {
static SNTEventLog *logger;
static SNTEventLog *logger = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
switch ([[SNTConfigurator configurator] eventLogType]) {
@@ -434,6 +443,10 @@
logger = [[SNTFileEventLog alloc] init];
break;
}
case SNTEventLogTypeProtobuf: {
logger = [[SNTProtobufEventLog alloc] init];
break;
}
default: logger = nil;
}
});

View File

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

View 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

View File

@@ -0,0 +1,21 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santad/Logs/SNTEventLog.h"
@interface SNTProtobufEventLog : SNTEventLog
- (void)logFileModification:(santa_message_t)message;
@end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@
#import <MOLCertificate/MOLCertificate.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
#import "Source/common/SNTCommon.h"
@class MOLCodesignChecker;
@class SNTCachedDecision;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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];
}
}

View File

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

View File

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

View File

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

View File

@@ -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"'
````

View File

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

View File

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

View File

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

View File

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

View File

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