Compare commits

..

1 Commits

248 changed files with 7942 additions and 14859 deletions

View File

@@ -1,2 +0,0 @@
build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
build --host_force_python=PY2

6
.gitignore vendored
View File

@@ -1,9 +1,9 @@
.DS_Store
default.profraw
*.provisionprofile
bazel-*
Build
santa-*
Pods
Santa.xcodeproj/xcuserdata
Santa.xcodeproj/project.xcworkspace
Santa.xcworkspace/xcuserdata
Santa.xcworkspace/xcshareddata
Source/DevelopmentTeam.xcconfig

View File

@@ -1,14 +1,14 @@
---
os: osx
osx_image: xcode11
language: objective-c
cache:
- bundler
- cocoapods
sudo: false
osx_image: xcode7
addons:
homebrew:
taps: bazelbuild/tap
packages: bazelbuild/tap/bazel
before_install:
- gem install cocoapods xcpretty
- pod setup >/dev/null
script:
- bazel build :release --show_progress_rate_limit=30.0 -c opt --apple_generate_dsym --color=no --verbose_failures --sandbox_debug
- bazel test :unit_tests --show_progress_rate_limit=30.0 --test_output=errors --color=no --verbose_failures --sandbox_debug
- xcodebuild -workspace Santa.xcworkspace -scheme All -derivedDataPath build build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}

167
BUILD
View File

@@ -1,167 +0,0 @@
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
exports_files(["LICENSE"])
# The version label for mac_* rules.
apple_bundle_version(
name = "version",
build_version = SANTA_VERSION,
short_version_string = SANTA_VERSION,
)
# Used to detect optimized builds
config_setting(
name = "opt_build",
values = {"compilation_mode": "opt"},
)
################################################################################
# Loading/Unloading/Reloading
################################################################################
run_command(
name = "unload",
cmd = """
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 kextunload -b com.google.santa-driver 2>/dev/null
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
""",
)
run_command(
name = "load",
cmd = """
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
launchctl load /Library/LaunchAgents/com.google.santa.plist
""",
)
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-bin/Source/santa_driver/santa_driver.zip >/dev/null
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa/Santa.zip >/dev/null
echo "You may be asked for your password for sudo"
sudo BINARIES=/tmp/bazel_santa_reload CONF=$${BUILD_WORKSPACE_DIRECTORY}/Conf \
$${BUILD_WORKSPACE_DIRECTORY}/Conf/install.sh
rm -rf /tmp/bazel_santa_reload
echo "Time to stop being naughty"
""",
)
################################################################################
# Release rules - used to create a release tarball
################################################################################
genrule(
name = "release",
srcs = [
"//Source/santa:Santa",
"//Source/santa_driver",
"Conf/install.sh",
"Conf/uninstall.sh",
"Conf/com.google.santa.bundleservice.plist",
"Conf/com.google.santad.plist",
"Conf/com.google.santa.plist",
"Conf/com.google.santa.asl.conf",
"Conf/com.google.santa.newsyslog.conf",
"Conf/Package/Makefile",
"Conf/Package/postinstall",
"Conf/Package/preinstall",
],
outs = ["santa-" + 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 and Santa.zip
for SRC in $(SRCS); do
if [ "$$(basename $${SRC})" == "santa_driver.zip" -o "$$(basename $${SRC})" == "Santa.zip" ]; then
mkdir -p $(@D)/binaries
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
fi
done
# Copy config files
for SRC in $(SRCS); do
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
mkdir -p $(@D)/conf
cp $${SRC} $(@D)/conf/
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
;;
*santad.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santad.dSYM
;;
*santactl.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santactl.dSYM
;;
*santabundleservice.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
;;
*Santa.app.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.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,conf,dsym} -exec touch {} \\;
# Create final output tar
tar -C $(@D) -czpf $(@) binaries dsym conf
""",
}),
heuristic_label_expansion = 0,
)
test_suite(
name = "unit_tests",
tests = [
"//Source/common:SNTFileInfoTest",
"//Source/common:SNTPrefixTreeTest",
"//Source/santa_driver:SantaCacheTest",
"//Source/santactl:SNTCommandFileInfoTest",
"//Source/santactl:SNTCommandSyncTest",
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
"//Source/santad:SNTRuleTableTest",
],
)

View File

@@ -32,7 +32,6 @@ PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/rele
# | |-- com.google.santad.plist
# | |-- com.google.santagui.plist
# | +-- com.google.santa.asl.conf
# | +-- com.google.santa.newsyslog.conf
# +--dsym
# |-- santa-driver.kext.dSYM
# |-- Santa.app.dSYM
@@ -45,7 +44,6 @@ PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
pack-Library-LaunchDaemons-com.google.santad.plist \
pack-Library-LaunchAgents-com.google.santagui.plist \
pack-etc-asl-com.google.santa.asl.conf \
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf \
pack-script-preinstall \
pack-script-postinstall
@@ -54,7 +52,6 @@ Santa.app: download
com.google.santad.plist: download
com.google.santagui.plist: download
com.google.santa.asl.conf: download
com.google.santa.newsyslog.conf: download
download:
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
@@ -68,12 +65,6 @@ pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
@sudo chmod 755 ${WORK_D}/private/etc/asl
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf: com.google.santa.newsyslog.conf l_private_etc
@sudo mkdir -p ${WORK_D}/private/etc/newsyslog.d
@sudo chown root:wheel ${WORK_D}/private/etc/newsyslog.d
@sudo chmod 755 ${WORK_D}/private/etc/newsyslog.d
@sudo install -m 644 -o root -g wheel com.google.santa.newsyslog.conf ${WORK_D}/private/etc/newsyslog.d
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
@sudo mkdir -p ${WORK_D}/Library/Extensions
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
@@ -88,7 +79,6 @@ myclean:
@rm -rf santa-driver.kext
@rm -f config.plist
@rm -f com.google.santa.asl.conf
@rm -f com.google.santa.newsyslog.conf
@rm -f com.google.santad.plist
@rm -f com.google.santagui.plist
@rm -f install.sh

View File

@@ -9,28 +9,20 @@
# Restart syslogd to pick up ASL configuration change
/usr/bin/killall -HUP syslogd
/sbin/kextload /Library/Extensions/santa-driver.kext
sleep 1
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
sleep 1
# Create hopefully useful symlink for santactl
mkdir -p /usr/local/bin
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
if [ $(uname -r | cut -d'.' -f1) -ge 19 ]; then
# Running on 10.15+
echo "Santa postinstall: running on 10.15+"
/bin/rm -rf /Library/Extensions/santa-driver.kext
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
else
# Running on <10.15
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
fi
# Load the bundle service
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin/santactl
user=$(/usr/bin/stat -f '%u' /dev/console)
if [[ -z "$user" ]]; then
/Applications/Santa.app/Contents/MacOS/Santa --load-system-extension
exit 0
fi
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santa.plist
[[ -z "$user" ]] && exit 0
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
exit 0

View File

@@ -6,24 +6,21 @@
[[ $3 != "/" ]] && exit 0
/bin/launchctl remove com.google.santad || true
/bin/launchctl remove com.google.santa.bundleservice || true
/bin/launchctl remove com.google.santad
/bin/sleep 1
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1 || true
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Remove cruft from old Santa versions
/bin/rm -f /usr/libexec/santad
/bin/rm -f /usr/sbin/santactl
/bin/rm /usr/libexec/santad
/bin/rm /usr/sbin/santactl
/bin/launchctl remove com.google.santasync
/bin/rm -f /Library/LaunchDaemons/com.google.santasync.plist
/bin/rm -rf /Applications/Santa.app
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist
/bin/sleep 1
sleep 1
user=$(/usr/bin/stat -f '%u' /dev/console)
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santa
exit 0

View File

@@ -1,6 +1,6 @@
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
> /var/db/santa/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
> /var/log/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
? [= Sender kernel] [S= Message santa-driver:] claim
? [= Sender kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
? [= Sender kernel] [S= Message santa-driver:] file /var/log/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/db/santa/santa.log
? [= Facility com.google.santa] file /var/log/santa.log

View File

@@ -1,26 +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>Label</key>
<string>com.google.santa.bundleservice</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/santabundleservice</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>com.google.santa.bundleservice</key>
<true/>
</dict>
<key>RunAtLoad</key>
<false/>
<key>KeepAlive</key>
<false/>
<key>ProcessType</key>
<string>Interactive</string>
<key>ThrottleInterval</key>
<integer>0</integer>
</dict>
</plist>

View File

@@ -1,2 +0,0 @@
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
/var/db/santa/santa.log root:wheel 644 10 25000 * NZ

View File

@@ -1,24 +1,26 @@
<?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">
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.google.santad</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>com.google.santa.daemon</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ProcessType</key>
<string>Interactive</string>
<key>Label</key>
<string>com.google.santad</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>SantaXPCControl</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true />
<key>ProcessType</key>
<string>Interactive</string>
<key>ThrottleInterval</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.google.santa</string>
<string>com.google.santagui</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>

12
Conf/config.plist Normal file
View File

@@ -0,0 +1,12 @@
<?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>
<!-- Minimal Configuration -->
<key>ClientMode</key>
<integer>1</integer>
<!-- For documentation of other keys, see the following URL:
https://github.com/google/santa/wiki/Configuration-Keys -->
</dict>
</plist>

View File

@@ -5,77 +5,55 @@ if [[ $EUID -ne 0 ]]; then
exit 1
fi
if [[ -z "${BINARIES}" || -z "${CONF}" ]]; then
if [[ -d "binaries" ]]; then
BINARIES="${PWD}/binaries"
CONF="${PWD}/conf"
elif [[ -d "../binaries" ]]; then
BINARIES="${PWD}/../binaries"
CONF="${PWD}/../conf"
else
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
exit 1
fi
if [[ -d "binaries" ]]; then
SOURCE="."
elif [[ -d "../binaries" ]]; then
SOURCE=".."
else
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
exit 1
fi
# Unload santad and scheduled sync job.
/bin/launchctl remove com.google.santad >/dev/null 2>&1
# Unload bundle service
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Determine if anyone is logged into the GUI
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Unload santad and scheduled sync job.
/bin/launchctl remove com.google.santad >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Unload GUI agent if someone is logged in.
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove com.google.santagui
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove com.google.santa
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove /Library/LaunchAgents/com.google.santagui.plist
# Cleanup cruft from old versions
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist >/dev/null 2>&1
/bin/rm /usr/libexec/santad >/dev/null 2>&1
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
/bin/rm -rf /Applications/Santa.app 2>&1
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
# Copy new files.
/bin/mkdir -p /var/db/santa
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
mkdir -p /usr/local/bin
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
/bin/cp -r ${BINARIES}/Santa.app /Applications
/bin/mkdir -p /usr/local/bin
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
# Reload syslogd to pick up ASL configuration change.
/usr/bin/killall -HUP syslogd
# Only copy the kext and load santad if running pre-10.15
if [ $(uname -r | cut -d'.' -f1) -lt 19 ]; then
/bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
else
/Applications/Santa.app/Contents/MacOS/Santa --load-system-extension
fi
# Load kext.
/sbin/kextload /Library/Extensions/santa-driver.kext
# Load the bundle service
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load santad and scheduled sync jobs.
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
# Load GUI agent if someone is logged in.
if [[ -n "$GUI_USER" ]]; then
/bin/launchctl asuser ${GUI_USER} \
/bin/launchctl load -w /Library/LaunchAgents/com.google.santa.plist
fi
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
exit 0

View File

@@ -6,28 +6,20 @@
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
# For macOS 10.15+ this will block up to 60 seconds
/Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
/bin/launchctl remove com.google.santad
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
user=$(/usr/bin/stat -f '%u' /dev/console)
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santa
# and to clean out the log config, although it won't write after wiping the binary
/usr/bin/killall -HUP syslogd
# delete artifacts on-disk
/bin/rm -rf /Applications/Santa.app
/bin/rm -rf /Library/Extensions/santa-driver.kext
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
/bin/rm -f /usr/local/bin/santactl # just a symlink
#uncomment to remove the config file and all databases, log files
#/bin/rm -rf /var/db/santa
#/bin/rm -f /var/log/santa*

View File

@@ -1,4 +0,0 @@
bin
llvm-*.src
llvm-*.src.tar.xz

View File

@@ -1,109 +0,0 @@
#!/usr/bin/env bash
LLVM_VERSION='5.0.1'
LLVM_COMPILERRT_TARBALL_NAME="llvm-${LLVM_VERSION}.src.tar.xz"
LLVM_COMPILERRT_SRC_FOLDER_NAME=`echo "${LLVM_COMPILERRT_TARBALL_NAME}" | cut -d '.' -f 1-4`
LLVM_COMPILERRT_TARBALL_URL="http://releases.llvm.org/${LLVM_VERSION}/${LLVM_COMPILERRT_TARBALL_NAME}"
LIBFUZZER_FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LOG_FILE=`mktemp`
main() {
echo "libFuzzer build script"
echo " > Checking dependencies..."
checkDependencies || return 1
echo " > Entering libFuzzer folder..."
cd "${LIBFUZZER_FOLDER}" > /dev/null 2>&1
if [ $? -ne 0 ] ; then
echo "Failed to enter the libFuzzer folder: ${LIBFUZZER_FOLDER}"
return 1
fi
if [ ! -f "${LLVM_COMPILERRT_TARBALL_NAME}" ] ; then
echo " > Downloading the LLVM tarball..."
curl "${LLVM_COMPILERRT_TARBALL_URL}" -o "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to download the LLVM tarball"
return 1
fi
else
echo " > An existing LLVM tarball was found"
fi
if [ -d "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" ] ; then
echo " > Deleting existing LLVM folder..."
rm -rf "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to delete the existing source folder"
return 1
fi
fi
echo " > Extracting the LLVM tarball..."
tar xf "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
rm "${LLVM_COMPILERRT_TARBALL_NAME}" "${LLVM_COMPILERRT_SRC_FOLDER_NAME}"
dumpLogFile "Failed to extract the LLVM tarball"
return 1
fi
if [ -d "bin" ] ; then
echo " > Deleting existing bin folder..."
rm -rf "bin" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to delete the existing bin folder"
return 1
fi
fi
mkdir "bin" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to create the bin folder"
return 1
fi
echo " > Building libFuzzer..."
( cd "bin" && "../${LLVM_COMPILERRT_SRC_FOLDER_NAME}/lib/Fuzzer/build.sh" ) > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to build the library"
return 1
fi
printf "\nFinished building libFuzzer\n"
rm "${LOG_FILE}"
return 0
}
checkDependencies() {
executable_list=( "clang++" "curl" "tar" )
for executable in "${executable_list[@]}" ; do
which "${executable}" > /dev/null 2>&1
if [ $? -ne 0 ] ; then
echo "The following program was not found: ${executable}"
return 1
fi
done
return 0
}
dumpLogFile() {
if [ $# -eq 1 ] ; then
local message="$1"
else
local message="An error has occurred"
fi
printf "${message}\n"
printf "Log file follows\n===\n"
cat "${LOG_FILE}"
printf "\n===\n"
rm "${LOG_FILE}"
}
main $@
exit $?

View File

@@ -1,3 +0,0 @@
santacache.dSYM
santacache

View File

@@ -1,41 +0,0 @@
/// Copyright 2018 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 <SantaCache.h>
#include <iostream>
#include <cstdint>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
std::uint64_t fields[2] = {};
if (size > 16) {
std::cout << "Invalid size! Start with -max_len=16\n";
return 1;
}
std::memcpy(fields, data, size);
decision_cache.set(fields[0], fields[1]);
auto returned_value = decision_cache.get(fields[0]);
if (returned_value != fields[1]) {
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value << "\n";
return 1;
}
return 0;
}

View File

@@ -1,16 +0,0 @@
{
"rules": [
{
"rule_type": "BINARY",
"policy": "BLACKLIST",
"sha256": "2dc104631939b4bdf5d6bccab76e166e37fe5e1605340cf68dab919df58b8eda",
"custom_msg": "blacklist firefox"
},
{
"rule_type": "CERTIFICATE",
"policy": "BLACKLIST",
"sha256": "e7726cf87cba9e25139465df5bd1557c8a8feed5c7dd338342d8da0959b63c8d",
"custom_msg": "blacklist dash app certificate"
}
]
}

View File

@@ -1,62 +0,0 @@
/// Copyright 2018 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 <iostream>
#include <cstdint>
#include <vector>
#include <SNTCommandSyncRuleDownload.h>
#include <SNTCommandSyncState.h>
#include <SNTCommandSyncConstants.h>
#include <SNTRule.h>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
if (!buffer) {
return 0;
}
NSError *error;
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error];
if (!response) {
return 0;
}
if (![response isKindOfClass:[NSDictionary class]]) {
return 0;
}
if (![response objectForKey:kRules]) {
return 0;
}
SNTCommandSyncState *state = [[SNTCommandSyncState alloc] init];
if (!state) {
return 0;
}
SNTCommandSyncRuleDownload *obj = [[SNTCommandSyncRuleDownload alloc] initWithState:state];
if (!obj) {
return 0;
}
for (NSDictionary *ruleDict in response[kRules]) {
SNTRule *rule = [obj ruleFromDictionary:ruleDict];
if (rule) {
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
}
}
return 0;
}

View File

@@ -1 +0,0 @@
К'.p▒└G╗М┐║ЙSЮ╝и▌РУерЭxt1iАЫШ9ы*H╩4R"═©$-├Уww╙+Р╝╘[┼иу╧oС┬ОwRpЗя≤х°е

View File

@@ -1,55 +0,0 @@
/// Copyright 2018 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 <iostream>
#include <cstdint>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTCommandController.h"
#import "SNTRule.h"
#import "SNTXPCControlInterface.h"
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
if (size > 16) {
std::cerr << "Invalid buffer size of " << size
<< " (should be <= 16)" << std::endl;
return 1;
}
santa_vnode_id_t vnodeID = {};
std::memcpy(&vnodeID, data, size);
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;;
} else if (action == ACTION_RESPOND_DENY) {
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;;
} else if (action == ACTION_UNSET) {
std::cerr << "File does not exist in cache" << std::endl;;
}
}];
return 0;
}

View File

@@ -1,51 +0,0 @@
/// Copyright 2018 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 <iostream>
#include <cstdint>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTCommandController.h"
#import "SNTRule.h"
#import "SNTXPCControlInterface.h"
#pragma pack(push, 1)
#pragma pack(pop)
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
auto *eventId = reinterpret_cast<const std::uint64_t *>(data);
std::size_t eventIdCount = size / sizeof(std::uint64_t);
if (eventIdCount == 0) {
return 0;
}
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:eventIdCount];
for (std::size_t i = 0; i < eventIdCount; i++) {
auto id = [NSNumber numberWithInteger:eventId[i]];
[eventIds addObject:id];
}
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
return 0;
}

View File

@@ -1,73 +0,0 @@
/// Copyright 2018 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 <iostream>
#include <cstdint>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTCommandController.h"
#import "SNTRule.h"
#import "SNTXPCControlInterface.h"
#pragma pack(push, 1)
struct InputData {
std::uint32_t cleanSlate;
std::uint32_t state;
std::uint32_t type;
char hash[33];
};
#pragma pack(pop)
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
if (size > sizeof(InputData)) {
std::cerr << "Invalid buffer size of " << size
<< " (should be <= " << sizeof(InputData)
<< ")" << std::endl;
return 1;
}
InputData input_data = {};
std::memcpy(&input_data, data, size);
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = (SNTRuleState) input_data.state;
newRule.type = (SNTRuleType) input_data.type;
newRule.shasum = @(input_data.hash);
newRule.customMsg = @"";
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
cleanSlate:NO
reply:^(NSError *error) {
if (!error) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
}
}
}];
return 0;
}

59
Podfile
View File

@@ -1,22 +1,49 @@
platform :osx, "10.9"
def common_pods
pod 'MOLXPCConnection'
pod 'MOLCodesignChecker'
pod 'FMDB'
pod 'MOLCertificate'
pod 'OCMock'
pod 'MOLAuthenticatingURLSession'
pod 'MOLFCMClient'
inhibit_all_warnings!
target :Santa do
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
end
project './Santa.xcodeproj'
target :santad do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
end
project = Xcodeproj::Project.open "./Santa.xcodeproj"
project.targets.each do |t|
if t.name == "santa-driver"
next
end
target t.name do
common_pods
target :santactl do
pod 'FMDB'
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'MOLFCMClient', '~> 1.3'
end
target :LogicTests do
pod 'FMDB'
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'OCMock'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.name != 'Release' then
break
end
# This is necessary to get FMDB to not NSLog stuff.
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
# Enable more compiler optimizations.
config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 'fast'
config.build_settings['LLVM_LTO'] = 'YES'
end
end
end

View File

@@ -1,46 +1,32 @@
PODS:
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- MOLAuthenticatingURLSession (2.4):
- MOLCertificate (~> 1.8)
- MOLCertificate (1.9)
- MOLCodesignChecker (1.10):
- MOLCertificate (~> 1.8)
- MOLFCMClient (1.8):
- MOLAuthenticatingURLSession (~> 2.4)
- MOLXPCConnection (1.2):
- MOLCodesignChecker (~> 1.9)
- OCMock (3.5)
- FMDB (2.6.2):
- FMDB/standard (= 2.6.2)
- FMDB/standard (2.6.2)
- MOLAuthenticatingURLSession (2.2):
- MOLCertificate (~> 1.5)
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- MOLFCMClient (1.3):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.4)
DEPENDENCIES:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient
- MOLXPCConnection
- MOLFCMClient (~> 1.3)
- OCMock
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient
- MOLXPCConnection
- OCMock
SPEC CHECKSUMS:
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
MOLFCMClient: 2bfbacd45cc11e1ca3c077e97b80401c4e4a54f1
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
OCMock: 4ab4577fc941af31f4a0398f6e7e230cf21fc72a
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
PODFILE CHECKSUM: d03767a9915896232523962c98d9ff7294aec2b7
PODFILE CHECKSUM: 1728b86e95e7cdb481dd91ab9c38af38e060a023
COCOAPODS: 1.7.5
COCOAPODS: 1.2.1

242
README.md
View File

@@ -1,183 +1,169 @@
# Santa [![Build Status][build-status-img]][build-status-link] [![Documentation Status][doc-status-img]][doc-status-link]
[build-status-img]: https://travis-ci.org/google/santa.png?branch=master
[build-status-link]: https://travis-ci.org/google/santa
[doc-status-img]: https://readthedocs.org/projects/santa/badge/?version=latest
[doc-status-link]: https://santa.readthedocs.io/en/latest/?badge=latest
Santa [![Build Status](https://travis-ci.org/google/santa.png?branch=master)](https://travis-ci.org/google/santa)
=====
<p align="center">
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
<a href="#santa--">
<img src="./Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
</a>
</p>
Santa is a binary whitelisting/blacklisting system for macOS. It consists of a
kernel extension (or a system extension on macOS 10.15+) that monitors for executions, a userland daemon that makes
execution decisions based on the contents of a SQLite 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 whitelisting/blacklisting system for macOS. It consists of
a kernel extension that monitors for executions, a userland daemon that makes
execution decisions based on the contents of a SQLite 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 not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
and finishing up a security audit.
It is named Santa because it keeps track of binaries that are naughty or nice.
Santa is a project of Google's Macintosh Operations Team.
# Docs
Admin-Related Features
========
The Santa docs are stored in the
[Docs](https://github.com/google/santa/blob/master/docs) directory. A Read the
Docs instance is available here: https://santa.readthedocs.io.
* Multiple modes: In the default MONITOR mode, all binaries except
those marked as blacklisted will be allowed to run, whilst being logged and recorded in the events database. In LOCKDOWN mode, only whitelisted binaries are
allowed to run.
The docs include deployment options, details on how parts of Santa work and
instructions for developing Santa itself.
* Event logging: When the kext is loaded, all binary launches are logged.
When in either mode, all unknown or denied binaries are stored in the database to enable later aggregation.
# Get Help
* Certificate-based rules, with override levels: Instead of relying on a binaries hash (or 'fingerprint'), executables can be whitelisted/blacklisted by their signing
certificate. You can therefore trust/block all binaries by a given publisher that were signed with that cert across version updates. A
binary can only be whitelisted by its certificate if its signature validates
correctly, but a rule for a binaries fingerprint will override a decision for a
certificate; i.e. you can whitelist a certificate while blacklisting a binary
signed with that certificate, or vice-versa.
If you have questions or otherwise need help getting started,
the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
great place.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature as Managed Client for OS X's (the precursor to configuration profiles, which used the same implementation mechanism) Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and doesn't rely on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precendence.
If you believe you have a bug, feel free to report [an
issue](https://github.com/google/santa/isues) and we'll respond as soon as we
can.
# Admin-Related Features
* Multiple modes: In the default MONITOR mode, all binaries except those marked
as blacklisted will be allowed to run, whilst being logged and recorded in
the events database. In LOCKDOWN mode, only whitelisted binaries are allowed
to run.
* Event logging: When the kext is loaded, all binary launches are logged. When
in either mode, all unknown or denied binaries are stored in the database to
enable later aggregation.
* Certificate-based rules, with override levels: Instead of relying on a
binary's hash (or 'fingerprint'), executables can be whitelisted/blacklisted
by their signing certificate. You can therefore trust/block all binaries by a
given publisher that were signed with that cert across version updates. A
binary can only be whitelisted by its certificate if its signature validates
correctly, but a rule for a binary's fingerprint will override a decision for
a certificate; i.e. you can whitelist a certificate while blacklisting a
binary signed with that certificate, or vice-versa.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature
to that found in Managed Client (the precursor to configuration profiles,
which used the same implementation mechanism), Application Launch
Restrictions via the mcxalr binary. This implementation carries the added
benefit of being configurable via regex, and not relying on LaunchServices.
As detailed in the wiki, when evaluating rules this holds the lowest
precedence.
* Failsafe cert rules: You cannot put in a deny rule that would block the
certificate used to sign launchd, a.k.a. pid 1, and therefore all components
used in macOS. The binaries in every OS update (and in some cases entire new
versions) are therefore auto-whitelisted. This does not affect binaries from
Apple's App Store, which use various certs that change regularly for common
apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct
separate cert than other Google apps.
# Intentions and Expectations
* Failsafe cert rules: You cannot put in a deny rule that would block the certificate used to sign launchd, a.k.a. pid 1, and therefore all components used in macOS. The binaries in every OS update (and in some cases entire new versions) are therefore auto-whitelisted. This does not affect binaries from Apple's App Store, which use various certs that change regularly for common apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct separate cert than other Google apps.
Intentions and Expectations
===========================
No single system or process will stop *all* attacks, or provide 100% security.
Santa is written with the intention of helping protect users from themselves.
People often download malware and trust it, giving the malware credentials, or
allowing unknown software to exfiltrate more data about your system. As a
centrally managed component, Santa can help stop the spread of malware among a
large fleet of machines. Independently, Santa can aid in analyzing what is
larger fleet of machines. Independently, Santa can aid in analyzing what is
running on your computer.
Santa is part of a defense-in-depth strategy, and you should continue to
protect hosts in whatever other ways you see fit.
Santa is part of a defense-in-depth strategy, and you should continue to protect
hosts in whatever other ways you see fit.
# Security and Performance-Related Features
Get Help
========
If you have questions or otherwise need help getting started, the
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
great place. Please consult the [wiki](https://github.com/google/santa/wiki) and [issues](https://github.com/google/santa/issues) as well.
Security and Performance-Related Features
============
* In-kernel caching: whitelisted binaries are cached in the kernel so the
processing required to make a request is only done if the binary isn't
already cached.
processing required to make a request is only done if the binary
isn't already cached.
* Userland components validate each other: each of the userland components (the
daemon, the GUI agent and the command-line utility) communicate with each
other using XPC and check that their signing certificates are identical
before any communication is accepted.
daemon, the GUI agent and the command-line utility) communicate with each other
using XPC and check that their signing certificates are identical before any
communication is accepted.
* Kext uses only KPIs: the kernel extension only uses provided kernel
programming interfaces to do its job. This means that the kext code should
continue to work across OS versions.
programming interfaces to do its job. This means that the kext code should
continue to work across OS versions.
# Known Issues
Known Issues
============
Santa is not yet a 1.0 and we have some known issues to be aware of:
* Santa only blocks execution (execve and variants), it doesn't protect against
dynamic libraries loaded with dlopen, libraries on disk that have been
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version
0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were
exploited in some versions of macOS. We are working on also protecting
against similar avenues of attack.
dynamic libraries loaded with dlopen, libraries on disk that have been replaced, or
libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version 0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were exploited in some versions of macOS. We are working on also protecting against similar avenues of attack.
* Kext communication security: the kext will only accept a connection from a
single client at a time and said client must be running as root. We haven't
yet found a good way to ensure the kext only accepts connections from a valid
client.
single client at a time and said client must be running as root. We haven't yet
found a good way to ensure the kext only accepts connections from a valid client.
* Database protection: the SQLite database is installed with permissions so
that only the root user can read/write it. We're considering approaches to
secure this further.
* Database protection: the SQLite database is installed with permissions so that
only the root user can read/write it. We're considering approaches to secure
this further.
* Sync client: The `santactl` command-line client includes a flag to synchronize with a management server, which uploads events that have occurred on the
machine and downloads new rules. We're still very heavily working on this
server (which is AppEngine-based and will be open-sourced in the future), so the
sync client code is unfinished. It does show the 'API' that we're expecting to
use so if you'd like to write your own management server, feel free to look at
how the client currently works (and suggest changes!)
* Scripts: Santa is currently written to ignore any execution that isn't a
binary. This is because after weighing the administration cost vs the
benefit, we found it wasn't worthwhile. Additionally, a number of
applications make use of temporary generated scripts, which we can't possibly
whitelist and not doing so would cause problems. We're happy to revisit this
(or at least make it an option) if it would be useful to others.
binary. This is because after weighing the administration cost vs the benefit,
we found it wasn't worthwhile. Additionally, a number of applications make use
of temporary generated scripts, which we can't possibly whitelist and not doing
so would cause problems. We're happy to revisit this (or at least make it an
option) if it would be useful to others.
# Sync Servers
* Documentation: This is currently limited.
* The `santactl` command-line client includes a flag to synchronize with a
management server, which uploads events that have occurred on the machine and
downloads new rules. There are several open-source servers you can sync with:
* Tests: There aren't enough of them.
* [Upvote](https://github.com/google/upvote) - An AppEngine-based server
that implements social voting to make managing a large fleet easier.
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
serves hardcoded rules from simple configuration files.
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
centralized service that pulls data from multiple sources and deploy
configurations to multiple services.
Screenshots
===========
* Alternatively, `santactl` can configure rules locally (without a sync
server).
A tool like Santa doesn't really lend itself to screenshots, so here's a video instead.
# Screenshots
<p align="center">
<img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif" alt="Santa Block Video" />
</p>
A tool like Santa doesn't really lend itself to screenshots, so here's a video
instead.
Building
========
Firstly, make sure you're using Xcode 7.3.1 as currently we do not support
building with Xcode 8.
<p align="center"> <img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif"
alt="Santa Block Video" /> </p>
```sh
git clone https://github.com/google/santa
cd santa
# Kext Signing
# Build a debug build. This will install any necessary CocoaPods, create the
# workspace and build, outputting the full log only if an error occurred.
# If CocoaPods is not installed, you'll be prompted to install it.
#
# For other build/install/run options, run rake without any arguments
rake build:debug
```
Note: the Xcode project is setup to use any installed "Mac Developer" certificate
and for security-reasons parts of Santa will not operate properly if not signed.
Kext Signing
============
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
Developer ID certificate with a kernel extension flag. Without it, the only way
to load an extension is to enable kext-dev-mode or disable SIP, depending on
the OS version.
to load an extension is to enable kext-dev-mode or disable SIP, depending on the
OS version.
There are two possible solutions for this, for distribution purposes:
1) Use a [pre-built, pre-signed
version](https://github.com/google/santa/releases) of the kext that we supply.
Each time changes are made to the kext code we will update the pre-built
version that you can make use of. This doesn't prevent you from making changes
to the non-kext parts of Santa and distributing those. If you make changes to
the kext and make a pull request, we can merge them in and distribute a new
version of the pre-signed kext.
1) Use a [pre-built, pre-signed version](https://github.com/google/santa/releases)
of the kext that we supply. Each time changes are made to the kext code we will
update the pre-built version that you can make use of. This doesn't prevent you
from making changes to the non-kext parts of Santa and distributing those.
If you make changes to the kext and make a pull request, we can merge them in
and distribute a new version of the pre-signed kext.
2) Apply for your own [kext signing
certificate](https://developer.apple.com/contact/kext/). Apple will only grant
this for broad distribution within an organization, they won't issue them just
for testing purposes.
2) Apply for your own [kext signing certificate](https://developer.apple.com/contact/kext/).
Apple will only grant this for broad distribution within an organization, they
won't issue them just for testing purposes.
# Contributing
Patches to this project are very much welcome. Please see the
[CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
Contributing
============
Patches to this project are very much welcome. Please see the [CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
file.
# Disclaimer
Disclaimer
==========
This is **not** an official Google product.

224
Rakefile Normal file
View File

@@ -0,0 +1,224 @@
require 'openssl'
WORKSPACE = 'Santa.xcworkspace'
DEFAULT_SCHEME = 'All'
OUTPUT_PATH = 'Build'
BINARIES = ['Santa.app', 'santa-driver.kext']
DSYMS = ['Santa.app.dSYM', 'santa-driver.kext.dSYM', 'santad.dSYM', 'santactl.dSYM']
XCPRETTY_DEFAULTS = '-sc'
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
DEVTEAM_FILE = 'Source/DevelopmentTeam.xcconfig'
DEVTEAM_CERT_CN = 'Mac Developer'
$DISABLE_XCPRETTY = false
task :default do
system("rake -sT")
end
def xcodebuild(opts)
command = "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts}"
if not $DISABLE_XCPRETTY
command << " | xcpretty #{XCPRETTY_DEFAULTS} && exit ${PIPESTATUS[0]}"
end
if system command
puts "\e[32mPass\e[0m"
else
raise "\e[31mFail\e[0m"
end
end
def xcodebuilddir
if not $xcode_build_dir
output = `xcodebuild #{XCODEBUILD_DEFAULTS} -scheme All -showBuildSettings`
if match = output.match(/BUILD_DIR = (.*)/)
$xcode_build_dir = match.captures.first
puts "Found Xcode build dir #{$xcode_build_dir}"
end
end
$xcode_build_dir
end
task :init do
unless File.exists?(WORKSPACE) and File.exists?('Pods')
puts "Pods missing, running 'pod install'"
system "pod install" or raise "CocoaPods is not installed. Install with 'sudo gem install cocoapods'"
end
unless system 'xcpretty -v >/dev/null 2>&1'
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
$DISABLE_XCPRETTY = true
end
cert_pem = `security find-certificate -p -c '#{DEVTEAM_CERT_CN}'`
cert = OpenSSL::X509::Certificate.new cert_pem
team_id = cert.subject.to_a.find {|f| f[0] == "OU"}[1]
File.open(DEVTEAM_FILE, 'w') { |f|
f.puts("// This file is auto-generated. Do not edit manually")
f.puts("DEVELOPMENT_TEAM = #{team_id}")
}
end
task :remove_existing do
system 'sudo rm -rf /Library/Extensions/santa-driver.kext'
system 'sudo rm -rf /Applications/Santa.app'
end
desc "Clean"
task :clean => :init do
puts "Cleaning"
FileUtils.rm_rf(OUTPUT_PATH)
xcodebuild("-scheme All clean")
end
# Build
namespace :build do
desc "Build: Debug"
task :debug do
Rake::Task['build:build'].invoke("Debug")
end
desc "Build: Release"
task :release do
Rake::Task['build:build'].invoke("Release")
end
task :build, [:configuration] => :init do |t, args|
config = args[:configuration]
puts "Building with configuration: #{config}"
xcodebuild("-scheme All -configuration #{config} build")
end
end
# Install
namespace :install do
desc "Install: Debug"
task :debug do
Rake::Task['install:install'].invoke("Debug")
end
desc "Install: Release"
task :release do
Rake::Task['install:install'].invoke("Release")
end
task :install, [:configuration] do |t, args|
config = args[:configuration]
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
Rake::Task['build:build'].invoke(config)
puts "Installing with configuration: #{config}"
Rake::Task['remove_existing'].invoke()
system "sudo cp -r #{xcodebuilddir}/#{config}/santa-driver.kext /Library/Extensions"
system "sudo cp -r #{xcodebuilddir}/#{config}/Santa.app /Applications"
end
end
# Dist
task :dist do
desc "Create distribution folder"
Rake::Task['clean'].invoke()
Rake::Task['build:build'].invoke("Release")
dist_path = "santa-#{`defaults read #{xcodebuilddir}/Release/santa-driver.kext/Contents/Info.plist CFBundleVersion`.strip}"
FileUtils.rm_rf(dist_path)
FileUtils.mkdir_p("#{dist_path}/binaries")
FileUtils.mkdir_p("#{dist_path}/conf")
FileUtils.mkdir_p("#{dist_path}/dsym")
BINARIES.each do |x|
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/binaries")
end
DSYMS.each do |x|
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/dsym")
end
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{dist_path}/conf")}
puts "Distribution folder #{dist_path} created"
end
# Tests
namespace :tests do
desc "Tests: Logic"
task :logic => [:init] do
puts "Running logic tests"
xcodebuild("-scheme LogicTests test")
end
desc "Tests: Kernel"
task :kernel do
Rake::Task['unload'].invoke()
Rake::Task['install:debug'].invoke()
Rake::Task['load_kext'].invoke
FileUtils.mkdir_p("/tmp/santa_kerneltests_tmp")
begin
puts "\033[?25l\033[12h" # hide cursor
puts "Running kernel tests"
system "cd /tmp/santa_kerneltests_tmp && sudo #{xcodebuilddir}/Debug/KernelTests"
rescue Exception
ensure
puts "\033[?25h\033[12l\n\n" # unhide cursor
FileUtils.rm_rf("/tmp/santa_kerneltests_tmp")
Rake::Task['unload_kext'].execute
end
end
end
# Load/Unload
task :unload_daemon do
puts "Unloading daemon"
system "sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null"
end
task :unload_kext do
puts "Unloading kernel extension"
system "sudo kextunload -b com.google.santa-driver 2>/dev/null"
end
task :unload_gui do
puts "Unloading GUI agent"
system "launchctl unload /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
end
desc "Unload"
task :unload => [:unload_daemon, :unload_kext, :unload_gui]
task :load_daemon do
puts "Loading daemon"
system "sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist"
end
task :load_kext do
puts "Loading kernel extension"
system "sudo kextload /Library/Extensions/santa-driver.kext"
end
task :load_gui do
puts "Loading GUI agent"
system "launchctl load /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
end
desc "Load"
task :load => [:load_kext, :load_daemon, :load_gui]
namespace :reload do
desc "Reload: Debug"
task :debug do
Rake::Task['unload'].invoke()
Rake::Task['install:debug'].invoke()
Rake::Task['load'].invoke()
end
desc "Reload: Release"
task :release do
Rake::Task['unload'].invoke()
Rake::Task['install:release'].invoke()
Rake::Task['load'].invoke()
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
BuildableName = "All"
BlueprintName = "All"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
enableAddressSanitizer = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
BuildableName = "LogicTests.xctest"
BlueprintName = "LogicTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
BuildableName = "All"
BlueprintName = "All"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
BuildableName = "KernelTests"
BlueprintName = "KernelTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
BuildableName = "KernelTests"
BlueprintName = "KernelTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
BuildableName = "KernelTests"
BlueprintName = "KernelTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
BuildableName = "KernelTests"
BlueprintName = "KernelTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
BuildableName = "LogicTests.xctest"
BlueprintName = "LogicTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
BuildableName = "LogicTests.xctest"
BlueprintName = "LogicTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
BuildableName = "LogicTests.xctest"
BlueprintName = "LogicTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
BuildableName = "LogicTests.xctest"
BlueprintName = "LogicTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
BuildableName = "LogicTests.xctest"
BlueprintName = "LogicTests"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,7 +14,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
BuildableName = "Santa.app"
BlueprintName = "Santa"
ReferencedContainer = "container:Santa.xcodeproj">
@@ -29,6 +29,17 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
BuildableName = "Santa.app"
BlueprintName = "Santa"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -44,12 +55,14 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
BuildableName = "Santa.app"
BlueprintName = "Santa"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -61,7 +74,7 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
BuildableName = "Santa.app"
BlueprintName = "Santa"
ReferencedContainer = "container:Santa.xcodeproj">

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
BuildableName = "santa-driver.kext"
BlueprintName = "santa-driver"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
BuildableName = "santa-driver.kext"
BlueprintName = "santa-driver"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
BuildableName = "santactl"
BlueprintName = "santactl"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
BuildableName = "santactl"
BlueprintName = "santactl"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
BuildableName = "santactl"
BlueprintName = "santactl"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
BuildableName = "santactl"
BlueprintName = "santactl"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,9 +14,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
BuildableName = "com.google.santa.daemon"
BlueprintName = "com.google.santa.daemon"
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
BuildableName = "santad"
BlueprintName = "santad"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -29,6 +29,17 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
BuildableName = "santad"
BlueprintName = "santad"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -45,12 +56,14 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
BuildableName = "com.google.santa.daemon"
BlueprintName = "com.google.santa.daemon"
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
BuildableName = "santad"
BlueprintName = "santad"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -62,9 +75,9 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
BuildableName = "com.google.santa.daemon"
BlueprintName = "com.google.santa.daemon"
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
BuildableName = "santad"
BlueprintName = "santad"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>

View File

@@ -1,10 +1 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Santa.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version='1.0' encoding='UTF-8'?><Workspace version='1.0'><FileRef location='group:Santa.xcodeproj'/><FileRef location='group:Pods/Pods.xcodeproj'/></Workspace>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14E46" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
@@ -13,10 +13,10 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>
@@ -29,13 +29,12 @@
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="VVj-gU-bzy">
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.1869618941" green="0.1869618941" blue="0.1869618941" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uh6-q0-RzL">
<rect key="frame" x="18" y="65" width="444" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="CcT-ul-1eA">
<font key="font" metaFont="system"/>
<string key="title">Santa is an application whitelisting system for macOS.

View File

@@ -1,34 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
<deployment identifier="macosx"/>
<development version="6300" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
<connections>
<outlet property="applicationNameLabel" destination="qgf-Jf-cJr" id="1JX-X8-03v"/>
<outlet property="bundleHashLabel" destination="xP7-jE-NF8" id="i8B-Gs-2E3"/>
<outlet property="bundleHashTitle" destination="MhO-U0-MLR" id="KT0-bK-fpV"/>
<outlet property="foundFileCountLabel" destination="LHV-gV-vyf" id="Sr0-T2-xGx"/>
<outlet property="hashingIndicator" destination="VyY-Yg-JOe" id="Yq4-tZ-9ep"/>
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="1577"/>
<rect key="contentRect" x="167" y="107" width="497" height="439"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="540" height="479"/>
<rect key="frame" x="0.0" y="0.0" width="497" height="439"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="16" y="451" width="37" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<rect key="frame" x="-6" y="411" width="37" height="32"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -40,14 +36,28 @@
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="vl5-A8-O0H"/>
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="43" y="369" width="454" height="17"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="206" y="368" width="85" height="41"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="z5y-RR-IEH"/>
</connections>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="22" y="329" width="454" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@@ -55,10 +65,58 @@
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="VC7-bE-uHc"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="297" width="142" height="17"/>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="146" y="132" width="1" height="167"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="282" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</textField>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="282" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="257" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
@@ -70,9 +128,9 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="272" width="142" height="17"/>
<rect key="frame" x="8" y="232" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
<constraint firstAttribute="width" constant="116" id="3wU-P0-gAC"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="adC-be-Beh">
<font key="font" metaFont="systemBold"/>
@@ -83,14 +141,14 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Binary Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="297" width="315" height="17"/>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="257" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Name" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@@ -98,7 +156,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<rect key="frame" x="8" y="247" width="142" height="17"/>
<rect key="frame" x="8" y="207" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -109,13 +167,13 @@
</textFieldCell>
</textField>
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="247" width="315" height="17"/>
<rect key="frame" x="165" y="207" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Dem-wH-KHm"/>
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
</constraints>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Code signing information" placeholderString="" id="ztA-La-XgT">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@@ -127,7 +185,7 @@
</connections>
</textField>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="62" y="248" width="15" height="15"/>
<rect key="frame" x="40" y="208" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
@@ -149,7 +207,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
<rect key="frame" x="8" y="222" width="142" height="17"/>
<rect key="frame" x="8" y="182" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -159,14 +217,14 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="187" y="222" width="219" height="17"/>
<textField verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="182" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="SHA-256" id="X4W-9e-eIu">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
@@ -176,26 +234,8 @@
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MhO-U0-MLR" userLabel="Label: Bundle Identifier">
<rect key="frame" x="8" y="197" width="142" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bundle Identifier" id="LEe-u0-52o">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.needsBundleHash" id="2kb-3z-Kyn">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
<rect key="frame" x="8" y="157" width="142" height="17"/>
<rect key="frame" x="8" y="157" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -206,13 +246,13 @@
</textFieldCell>
</textField>
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="157" width="294" height="17"/>
<rect key="frame" x="165" y="157" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
@@ -229,7 +269,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<rect key="frame" x="8" y="132" width="142" height="17"/>
<rect key="frame" x="8" y="132" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -239,8 +279,26 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="132" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="154" y="34" width="112" height="23"/>
<rect key="frame" x="132" y="33" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
@@ -257,35 +315,8 @@ DQ
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="272" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="113" y="80" width="315" height="29"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="278" y="34" width="110" height="23"/>
<rect key="frame" x="256" y="33" width="110" height="25"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
@@ -304,152 +335,61 @@ DQ
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
</connections>
</button>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="132" width="294" height="17"/>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="232" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
<constraint firstAttribute="width" constant="290" id="p1W-f9-KBX"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
</connections>
</textField>
<progressIndicator wantsLayer="YES" canDrawConcurrently="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
<rect key="frame" x="187" y="199" width="217" height="12"/>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="91" y="80" width="315" height="29"/>
<constraints>
<constraint firstAttribute="width" constant="217" id="M22-Dv-KIP"/>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
</progressIndicator>
<textField toolTip="Bundle SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xP7-jE-NF8">
<rect key="frame" x="187" y="197" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="s7W-o9-2nN"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="Calculating..." id="yJa-yL-X9a">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileBundleHash" id="CnT-q6-bot"/>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LHV-gV-vyf">
<rect key="frame" x="187" y="182" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="LUu-Vd-peN"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="1000 related binaries" id="AVM-vB-hB8">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<box horizontalHuggingPriority="750" boxType="custom" borderType="line" title="Line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="168" y="132" width="1" height="207"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="322" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Pav-ZA-iAu"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="322" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="8mA-zi-Ev7"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="229" y="408" width="82" height="41"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="z5y-RR-IEH"/>
</connections>
</textField>
</button>
</subviews>
<constraints>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="0AD-PS-5V1"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="5Mr-By-PAU"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="centerY" secondItem="qgf-Jf-cJr" secondAttribute="centerY" id="AKX-pe-hEX"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="ALv-0v-szi"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="E6D-7P-17g"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="G0I-O2-S91"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-67" id="GD2-Ka-deo"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="GT2-tO-2td"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="centerY" secondItem="oFj-ol-xpL" secondAttribute="centerY" id="GXI-pT-FM1"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="top" secondItem="pDa-fA-vnC" secondAttribute="top" id="Gd4-Nr-n5G"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="HUT-MI-jsR"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="Ht4-Lg-U5N"/>
<constraint firstItem="LHV-gV-vyf" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="IA0-dy-2be"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="IwX-ja-ZIs"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" priority="500" id="JY4-N1-j8e"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="centerX" secondItem="Iwq-Lx-rLv" secondAttribute="centerX" id="LkH-F4-Ncm"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="55" id="Nsl-zf-poH"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="20" id="Seb-c0-MUL"/>
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
<constraint firstItem="LHV-gV-vyf" firstAttribute="top" secondItem="VyY-Yg-JOe" secondAttribute="bottom" id="Vjr-NX-j8V"/>
<constraint firstItem="MhO-U0-MLR" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="Vly-VE-BwU"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="top" secondItem="bDE-Tl-UHg" secondAttribute="bottom" constant="8" id="ZoS-xV-2WA"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aMJ-Wb-vRS"/>
@@ -461,28 +401,22 @@ DQ
<constraint firstItem="qgf-Jf-cJr" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="esg-lX-BAT"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="fGd-YS-phP"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="LHV-gV-vyf" secondAttribute="bottom" constant="8" id="h4h-K3-BTd"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" priority="700" constant="8" id="hXw-6Z-lb2"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="7ua-5a-uSd" secondAttribute="trailing" constant="12" id="ioO-NJ-Jqo"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="jdk-ak-soQ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="lvJ-Rk-UT5" secondAttribute="centerY" id="jfs-YI-7Ae"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="MhO-U0-MLR" secondAttribute="trailing" constant="20" id="ke9-wW-5fr"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="top" secondItem="h6f-PY-cc0" secondAttribute="bottom" constant="25" id="lYd-VZ-lBs"/>
<constraint firstItem="VyY-Yg-JOe" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="18" id="lei-uP-T8m"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="xP7-jE-NF8" secondAttribute="bottom" priority="701" constant="8" id="oY4-e7-lsz"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="pCX-eX-erN"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="centerY" secondItem="MhO-U0-MLR" secondAttribute="centerY" id="pdC-x8-Nao"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="60" id="phL-j9-rPq"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="u1y-6V-moc"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="VyY-Yg-JOe" firstAttribute="centerY" secondItem="MhO-U0-MLR" secondAttribute="centerY" id="vB8-c5-pfO"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="YNz-ka-cBi" secondAttribute="trailing" constant="20" id="vfq-83-tKI"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="z6s-ga-iAk"/>
</constraints>
@@ -490,9 +424,8 @@ DQ
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="274" y="326.5"/>
<point key="canvasLocation" x="302.5" y="304.5"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="iXx-cu-WYe"/>
</objects>
<resources>
<image name="NSInfo" width="32" height="32"/>

View File

@@ -2,30 +2,26 @@
<!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</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Santa</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Google, Inc.</string>
<key>CFBundleIdentifier</key>
<string>com.google.${PRODUCT_NAME:rfc1034identifier}GUI</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<string>TO.BE.FILLED</string>
<key>CFBundleShortVersionString</key>
<string>TO.BE.FILLED</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_VERSION_MIN}</string>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Google LLC.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Cocoa/Cocoa.h>
@import Cocoa;
@interface SNTAboutWindowController : NSWindowController

View File

@@ -12,9 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTAboutWindowController.h"
#import "SNTAboutWindowController.h"
#import "Source/common/SNTConfigurator.h"
#import "SNTConfigurator.h"
@implementation SNTAboutWindowController

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Cocoa/Cocoa.h>
@import Cocoa;
/**
An NSTextField subclass that provides an accessiblity label equal to:

View File

@@ -12,10 +12,14 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTAccessibleTextField.h"
#import "SNTAccessibleTextField.h"
@implementation SNTAccessibleTextField
- (BOOL)accessibilityIsIgnored {
return NO;
}
- (NSString *)accessibilityLabel {
if (self.toolTip && self.stringValue) {
return [NSString stringWithFormat:@"%@: %@", self.toolTip, self.stringValue];

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Cocoa/Cocoa.h>
@import Cocoa;
///
/// Initiates and manages the connection to santad

View File

@@ -0,0 +1,124 @@
/// 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 "SNTAppDelegate.h"
#import "SNTAboutWindowController.h"
#import "SNTConfigurator.h"
#import "SNTFileWatcher.h"
#import "SNTNotificationManager.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;
@property SNTFileWatcher *configFileWatcher;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *listener;
@end
@implementation SNTAppDelegate
#pragma mark App Delegate methods
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setupMenu];
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
handler:^(unsigned long data) {
if (! (data & DISPATCH_VNODE_ATTRIB)) [[SNTConfigurator configurator] reloadConfigData];
}];
self.notificationManager = [[SNTNotificationManager alloc] init];
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidResignActiveNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
self.listener.invalidationHandler = nil;
[self.listener invalidate];
self.listener = nil;
}];
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
[self attemptReconnection];
}];
[self createConnection];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
[self.aboutWindowController showWindow:self];
return NO;
}
#pragma mark Connection handling
- (void)createConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
WEAKIFY(self);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
self.listener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptReconnection];
};
[self.listener resume];
// Tell daemon to connect back to the above listener.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] setNotificationListener:listener.endpoint];
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptReconnection];
}
}
- (void)attemptReconnection {
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
}
#pragma mark Menu Management
- (void)setupMenu {
// Whilst the user will never see the menu, having one with the Copy and Select All options
// allows the shortcuts for these items to work, which is useful for being able to copy
// information from notifications. The mainMenu must have a nested menu for this to work properly.
NSMenu *mainMenu = [[NSMenu alloc] init];
NSMenu *editMenu = [[NSMenu alloc] init];
[editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
[editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
NSMenuItem *editMenuItem = [[NSMenuItem alloc] init];
[editMenuItem setSubmenu:editMenu];
[mainMenu addItem:editMenuItem];
[NSApp setMainMenu:mainMenu];
}
@end

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Cocoa/Cocoa.h>
@import Cocoa;
///
/// An NSPanel that can become key/main and can fade in/out.

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTMessageWindow.h"
#import "SNTMessageWindow.h"
@implementation SNTMessageWindow

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Cocoa/Cocoa.h>
@import Cocoa;
@class SNTStoredEvent;
@@ -31,40 +31,6 @@
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashLabel;
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashTitle;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// The execution event that this window is for
///
@property(readonly) SNTStoredEvent *event;
///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSProgress *progress;
///
/// The delegate to inform when the notification is dismissed
///

View File

@@ -12,17 +12,21 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santa/SNTMessageWindowController.h"
#import "SNTMessageWindowController.h"
#import <MOLCertificate/MOLCertificate.h>
#import <SecurityInterface/SFCertificatePanel.h>
@import SecurityInterface.SFCertificatePanel;
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santa/SNTMessageWindow.h"
#import "MOLCertificate.h"
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTFileInfo.h"
#import "SNTMessageWindow.h"
#import "SNTStoredEvent.h"
@interface SNTMessageWindowController ()
/// The execution event that this window is for
@property SNTStoredEvent *event;
/// The custom message to display for this event
@property(copy) NSString *customMessage;
@@ -32,9 +36,13 @@
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
@property IBOutlet NSButton *openEventButton;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property(weak) IBOutlet NSTextField *applicationNameLabel;
@property IBOutlet NSTextField *applicationNameLabel;
/// Linked to checkbox in UI to prevent future notifications for this binary.
@property BOOL silenceFutureNotifications;
@@ -47,34 +55,10 @@
if (self) {
_event = event;
_customMessage = message;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
if (progress.fractionCompleted != 0.0) {
self.hashingIndicator.indeterminate = NO;
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
@@ -89,18 +73,6 @@
}
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];
[self.foundFileCountLabel removeFromSuperview];
} else {
self.openEventButton.enabled = NO;
self.hashingIndicator.indeterminate = YES;
[self.hashingIndicator startAnimation:self];
self.bundleHashLabel.hidden = YES;
self.foundFileCountLabel.stringValue = @"";
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
@@ -111,7 +83,6 @@
}
- (IBAction)closeWindow:(id)sender {
[self.progress cancel];
[(SNTMessageWindow *)self.window fadeOut:sender];
}

View File

@@ -12,16 +12,14 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Cocoa/Cocoa.h>
@import Cocoa;
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/santa/SNTMessageWindowController.h"
#import "SNTMessageWindowController.h"
#import "SNTXPCNotifierInterface.h"
///
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.
///
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate, SNTNotifierXPC>
@property NSXPCListenerEndpoint *notificationListener;
@end

View File

@@ -0,0 +1,146 @@
/// 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 "SNTNotificationManager.h"
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@interface SNTNotificationManager ()
/// The currently displayed notification
@property SNTMessageWindowController *currentWindowController;
/// The queue of pending notifications
@property(readonly) NSMutableArray *pendingNotifications;
@end
@implementation SNTNotificationManager
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
- (instancetype)init {
self = [super init];
if (self) {
_pendingNotifications = [[NSMutableArray alloc] init];
}
return self;
}
- (void)windowDidCloseSilenceHash:(NSString *)hash {
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
if ([self.pendingNotifications count]) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
} else {
[NSApp hide:self];
}
}
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
if (!d) d = [NSMutableDictionary dictionary];
if (date) {
d[hash] = date;
} else {
[d removeObjectForKey:hash];
}
[ud setObject:d forKey:silencedNotificationsKey];
}
#pragma mark SNTNotifierXPC protocol method
- (void)postClientModeNotification:(SNTClientMode)clientmode {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
NSString *customMsg;
switch (clientmode) {
case SNTClientModeMonitor:
un.informativeText = @"Switching into Monitor mode";
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
break;
case SNTClientModeLockdown:
un.informativeText = @"Switching into Lockdown mode";
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
break;
default:
return;
}
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
// See if this binary is already in the list of pending notifications.
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
// See if this binary is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else {
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
return;
}
}
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
// Notifications arrive on a background thread but UI updates must happen on the main thread.
// This includes making windows.
dispatch_async(dispatch_get_main_queue(), ^{
SNTMessageWindowController *pendingMsg =
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
// If a notification isn't currently being displayed, display the incoming one.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
}
});
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
un.informativeText = message ?: @"Requested application can now be run";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
@end

View File

@@ -1,4 +1,4 @@
/// Copyright 2019 Google Inc. All rights reserved.
/// 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.
@@ -12,10 +12,16 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Cocoa;
#include "Source/common/SNTKernelCommon.h"
#include "Source/santad/EventProviders/SNTEventProvider.h"
#import "SNTAppDelegate.h"
@interface SNTEndpointSecurityManager : NSObject<SNTEventProvider>
@end
int main(int argc, const char *argv[]) {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
SNTAppDelegate *delegate = [[SNTAppDelegate alloc] init];
[app setDelegate:delegate];
[app finishLaunching];
[app run];
}
}

View File

@@ -1,212 +0,0 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"]) # Apache 2.0
load("//:helper.bzl", "santa_unit_test")
objc_library(
name = "SNTBlockMessage",
srcs = ["SNTBlockMessage.m"],
hdrs = ["SNTBlockMessage.h"],
deps = [
":SNTConfigurator",
":SNTStoredEvent",
],
)
objc_library(
name = "SNTBlockMessage_SantaGUI",
srcs = ["SNTBlockMessage.m"],
hdrs = ["SNTBlockMessage.h"],
defines = ["SANTAGUI"],
deps = [
":SNTConfigurator",
":SNTStoredEvent",
],
)
objc_library(
name = "SNTCachedDecision",
srcs = ["SNTCachedDecision.m"],
hdrs = ["SNTCachedDecision.h"],
deps = [
":SNTCommonEnums",
":SNTKernelCommon",
],
)
cc_library(
name = "SNTCommonEnums",
hdrs = ["SNTCommonEnums.h"],
)
objc_library(
name = "SNTConfigurator",
srcs = ["SNTConfigurator.m"],
hdrs = ["SNTConfigurator.h"],
deps = [
":SNTCommonEnums",
":SNTLogging",
":SNTStrengthify",
":SNTSystemInfo",
],
)
objc_library(
name = "SNTDropRootPrivs",
srcs = ["SNTDropRootPrivs.m"],
hdrs = ["SNTDropRootPrivs.h"],
)
objc_library(
name = "SNTFileInfo",
srcs = ["SNTFileInfo.m"],
hdrs = ["SNTFileInfo.h"],
deps = [
"@FMDB",
"@MOLCodesignChecker",
],
)
cc_library(
name = "SNTKernelCommon",
hdrs = ["SNTKernelCommon.h"],
)
cc_library(
name = "SNTLoggingKernel",
hdrs = ["SNTLogging.h"],
)
objc_library(
name = "SNTLogging",
srcs = ["SNTLogging.m"],
hdrs = ["SNTLogging.h"],
)
cc_library(
name = "SNTPrefixTree",
srcs = ["SNTPrefixTree.cc"],
hdrs = ["SNTPrefixTree.h"],
copts = ["-std=c++11"],
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"],
deps = [":SNTLoggingKernel"],
)
objc_library(
name = "SNTRule",
srcs = ["SNTRule.m"],
hdrs = ["SNTRule.h"],
deps = [":SNTCommonEnums"],
)
objc_library(
name = "SNTStoredEvent",
srcs = ["SNTStoredEvent.m"],
hdrs = ["SNTStoredEvent.h"],
deps = [
":SNTCommonEnums",
"@MOLCertificate",
],
)
cc_library(
name = "SNTStrengthify",
hdrs = ["SNTStrengthify.h"],
)
objc_library(
name = "SNTSystemInfo",
srcs = ["SNTSystemInfo.m"],
hdrs = ["SNTSystemInfo.h"],
sdk_frameworks = ["IOKit"],
)
objc_library(
name = "SNTXPCBundleServiceInterface",
srcs = ["SNTXPCBundleServiceInterface.m"],
hdrs = ["SNTXPCBundleServiceInterface.h"],
deps = [
":SNTStoredEvent",
"@MOLXPCConnection",
],
)
objc_library(
name = "SNTXPCControlInterface",
srcs = ["SNTXPCControlInterface.m"],
hdrs = ["SNTXPCControlInterface.h"],
deps = [
":SNTConfigurator",
":SNTStoredEvent",
":SNTXPCUnprivilegedControlInterface",
"@MOLXPCConnection",
],
)
objc_library(
name = "SNTXPCNotifierInterface",
srcs = ["SNTXPCNotifierInterface.m"],
hdrs = ["SNTXPCNotifierInterface.h"],
deps = [
":SNTCommonEnums",
":SNTXPCBundleServiceInterface",
],
)
objc_library(
name = "SNTXPCSyncdInterface",
srcs = ["SNTXPCSyncdInterface.m"],
hdrs = ["SNTXPCSyncdInterface.h"],
deps = [
":SNTCommonEnums",
":SNTStoredEvent",
],
)
objc_library(
name = "SNTXPCUnprivilegedControlInterface",
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
deps = [
":SNTCommonEnums",
":SNTKernelCommon",
":SNTRule",
":SNTStoredEvent",
":SNTXPCBundleServiceInterface",
"@MOLCertificate",
"@MOLXPCConnection",
],
)
santa_unit_test(
name = "SNTFileInfoTest",
srcs = ["SNTFileInfoTest.m"],
resources = [
"testdata/bad_pagezero",
"testdata/missing_pagezero",
],
structured_resources = glob([
"testdata/BundleExample.app/**",
"testdata/DirectoryBundle/**",
]),
deps = [":SNTFileInfo"],
)
santa_unit_test(
name = "SNTPrefixTreeTest",
srcs = ["SNTPrefixTreeTest.mm"],
deps = ["SNTPrefixTree"],
)

View File

@@ -12,11 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifdef SANTAGUI
#import <Cocoa/Cocoa.h>
#else
#import <Foundation/Foundation.h>
#endif
@import Foundation;
@class SNTStoredEvent;

View File

@@ -12,11 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTBlockMessage.h"
#import "SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@implementation SNTBlockMessage
@@ -26,24 +26,10 @@
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: %@;"
@" color: #666;"
@" text-align: center;"
@"}"
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
@"@media (prefers-color-scheme: dark) {"
@" body {"
@" color: #ddd;"
@" }"
@"}"
@"</style></head><body>";
// Support Dark Mode. Note, the returned NSAttributedString is static and does not update when
// the OS switches modes.
NSString *mode = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"];
BOOL dark = [mode isEqualToString:@"Dark"];
htmlHeader = [NSString stringWithFormat:htmlHeader, dark ? @"#ddd" : @"#333"];
NSString *htmlFooter = @"</body></html>";
NSString *message;
@@ -65,7 +51,7 @@
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
#ifdef SANTAGUI
#ifdef NSAppKitVersionNumber10_0
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
#else
@@ -106,13 +92,25 @@
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *formatStr = config.eventDetailURL;
NSString *formatStr, *versionStr;
if (config.eventDetailBundleURL.length && event.fileBundleID) {
formatStr = config.eventDetailBundleURL;
versionStr = event.fileBundleVersion;
if (!versionStr) versionStr = event.fileBundleVersionString;
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
withString:event.fileBundleID];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
withString:versionStr];
} else {
formatStr = config.eventDetailURL;
}
if (!formatStr.length) return nil;
if (event.fileSHA256) {
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileSHA256];
}
if (event.executingUser) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
///
/// These enums are used in various places throughout the Santa client code.
@@ -33,9 +33,6 @@ typedef NS_ENUM(NSInteger, SNTRuleState) {
SNTRuleStateBlacklist = 2,
SNTRuleStateSilentBlacklist = 3,
SNTRuleStateRemove = 4,
SNTRuleStateWhitelistCompiler = 5,
SNTRuleStateWhitelistTransitive = 6,
};
typedef NS_ENUM(NSInteger, SNTClientMode) {
@@ -61,9 +58,6 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateAllowBinary = 1 << 25,
SNTEventStateAllowCertificate = 1 << 26,
SNTEventStateAllowScope = 1 << 27,
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
@@ -74,24 +68,10 @@ typedef NS_ENUM(NSInteger, SNTRuleTableError) {
SNTRuleTableErrorEmptyRuleArray,
SNTRuleTableErrorInsertOrReplaceFailed,
SNTRuleTableErrorInvalidRule,
SNTRuleTableErrorMissingRequiredRule,
SNTRuleTableErrorRemoveFailed
};
// This enum type is used to indicate what should be done with the related bundle events that are
// generated when an initiating blocked bundle event occurs.
typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
SNTBundleEventActionDropEvents,
SNTBundleEventActionStoreEvents,
SNTBundleEventActionSendEvents,
};
// Indicates where to store event logs.
typedef NS_ENUM(NSInteger, SNTEventLogType) {
SNTEventLogTypeSyslog,
SNTEventLogTypeFilelog,
};
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";
static const char *kSantaCtlPath = "/Applications/Santa.app/Contents/MacOS/santactl";
static const char *kSantaAppPath = "/Applications/Santa.app";
static const char *kSantaDPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santad";
static const char *kSantaCtlPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl";

View File

@@ -12,56 +12,25 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
#import "Source/common/SNTCommonEnums.h"
#import "SNTCommonEnums.h"
///
/// Singleton that provides an interface for managing configuration values on disk
/// @note This class is designed as a singleton but that is not strictly enforced.
/// @note All properties are KVO compliant.
///
@interface SNTConfigurator : NSObject
/// Default config file path
extern NSString *const kDefaultConfigFilePath;
#pragma mark - Daemon Settings
///
/// The operating mode.
///
@property(readonly, nonatomic) SNTClientMode clientMode;
///
/// Set the operating mode as received from a sync server.
///
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
///
/// The regex of whitelisted paths. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(readonly, nonatomic) NSRegularExpression *whitelistPathRegex;
///
/// Set the regex of whitelisted paths as received from a sync server.
///
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re;
///
/// The regex of blacklisted paths. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(readonly, nonatomic) NSRegularExpression *blacklistPathRegex;
///
/// Set the regex of blacklisted paths as received from a sync server.
///
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re;
@property(nonatomic) SNTClientMode clientMode;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
@@ -70,58 +39,25 @@
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(readonly, nonatomic) NSRegularExpression *fileChangesRegex;
@property(nonatomic) NSRegularExpression *fileChangesRegex;
///
/// A list of ignore prefixes which are checked in-kernel.
/// This is more performant than FileChangesRegex when ignoring whole directory trees.
/// The regex of whitelisted paths. Regexes are specified in ICU format.
///
/// For example adding a prefix of "/private/tmp/" will turn off file change log generation
/// in-kernel for that entire tree. Since they are ignored by the kernel, they never reach santad
/// and are not seen by the fileChangesRegex. Note the trailing "/", without it any file or
/// directory starting with "/private/tmp" would be ignored.
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
/// By default "/." and "/dev/" are added.
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
///
/// Memory in the kernel is precious. A total of MAXPATHLEN (1024) nodes are allowed.
/// Using all 1024 nodes will result in santa-driver allocating ~2MB of wired memory.
/// An ASCII character uses 1 node. An UTF-8 encoded Unicode character uses 1-4 nodes.
/// Prefixes are added to the running config in-order, one by one. The prefix will be ignored if
/// (the running config's current size) + (the prefix's size) totals up to more than 1024 nodes.
/// The running config is stored in a prefix tree.
/// Prefixes that share prefixes are effectively de-duped; their shared node sized components only
/// take up 1 node. For example these 3 prefixes all have a common prefix of "/private/".
/// They will only take up 21 nodes instead of 39.
/// The regex of blacklisted paths. Regexes are specified in ICU format.
///
/// "/private/tmp/"
/// "/private/var/"
/// "/private/new/"
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
/// -> [t] -> [m] -> [p] -> [/]
///
/// [/] -> [p] -> [r] -> [i] -> [v] -> [a] -> [t] -> [e] -> [/] -> [v] -> [a] -> [r] -> [/]
///
/// -> [n] -> [e] -> [w] -> [/]
///
/// Prefixes with Unicode characters work similarly. Assuming a UTF-8 encoding these two prefixes
/// are actually the same for the first 3 nodes. They take up 7 nodes instead of 10.
///
/// "/🤘"
/// "/🖖"
///
/// -> [0xa4] -> [0x98]
///
/// [/] -> [0xf0] -> [0x9f]
///
/// -> [0x96] -> [0x96]
///
/// To disable file change logging completely add "/".
/// TODO(bur): Make this default if no FileChangesRegex is set.
///
/// Filters are only applied on santad startup.
/// TODO(bur): Support add / remove of filters while santad is running.
///
@property(readonly, nonatomic) NSArray *fileChangesPrefixFilters;
@property(nonatomic) NSRegularExpression *blacklistPathRegex;
///
/// Enable __PAGEZERO protection, defaults to YES
@@ -130,49 +66,6 @@
///
@property(readonly, nonatomic) BOOL enablePageZeroProtection;
///
/// Enable bad signature protection, defaults to NO.
/// When enabled, a binary that is signed but has a bad signature (cert revoked, binary
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary whitelist
/// rule exists.
///
@property(readonly, nonatomic) BOOL enableBadSignatureProtection;
///
/// 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.
/// Defaults to SNTEventLogTypeFilelog.
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
///
/// @note: This property is KVO compliant, but should only be read once at santad startup.
///
@property(readonly, nonatomic) SNTEventLogType eventLogType;
///
/// If eventLogType is set to Filelog, eventLogPath will provide the path to save logs.
/// Defaults to /var/db/santa/santa.log.
///
/// @note: This property is KVO compliant, but should only be read once at santad startup.
///
@property(readonly, nonatomic) NSString *eventLogPath;
///
/// 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.
/// Defaults to NO.
///
@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;
#pragma mark - GUI Settings
///
@@ -184,6 +77,8 @@
///
/// When the user gets a block notification, a button can be displayed which will
/// take them to a web page with more information about that event.
/// There are two properties, one for individual binaries and one for binaries that are part
/// of a bundle. If the latter is not set the former will be used.
///
/// This property contains a kind of format string to be turned into the URL to send them to.
/// The following sequences will be replaced in the final URL:
@@ -191,12 +86,15 @@
/// %file_sha% -- SHA-256 of the file that was blocked.
/// %machine_id% -- ID of the machine.
/// %username% -- executing user.
/// %bundle_id% -- bundle id of the binary, if applicable.
/// %bundle_ver% -- bundle version of the binary, if applicable.
///
/// @note: This is not an NSURL because the format-string parsing is done elsewhere.
///
/// If this item isn't set, the Open Event button will not be displayed.
///
@property(readonly, nonatomic) NSString *eventDetailURL;
@property(readonly, nonatomic) NSString *eventDetailBundleURL;
///
/// Related to the above property, this string represents the text to show on the button.
@@ -235,6 +133,12 @@
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// If YES, mid-execution event uploads are skipped.
/// This property is never stored on disk.
///
@property BOOL syncBackOff;
///
/// The machine owner.
///
@@ -261,19 +165,22 @@
@property(readonly, nonatomic) NSString *machineID;
///
/// If YES, enables bundle detection for blocked events. This property is not stored on disk.
/// Its value is set by a sync server that supports bundles. Defaults to NO.
/// The number of seconds in-between full syncs while connected to FCM
///
@property BOOL enableBundles;
#pragma mark Transitive Whitelisting Settings
/// @note The default value is 14400.
/// @note The minimum value is 600.
///
@property(readonly, nonatomic) NSInteger FCMFullSyncInterval;
///
/// If YES, binaries marked with SNTRuleStateWhitelistCompiler rules are allowed to transitively
/// whitelist any executables that they produce. If NO, SNTRuleStateWhitelistCompiler rules are
/// interpreted as if they were simply SNTRuleStateWhitelist rules. Defaults to NO.
/// The maximum number of seconds to wait before a rule sync when
/// receiving a global rule sync message. Each client will choose a random
/// number of seconds between 0 and FCMGlobalRuleLeeway.
///
@property BOOL enableTransitiveWhitelisting;
/// @note The default value is 600.
/// @note The minimum value is 60.
///
@property(readonly, nonatomic) NSInteger FCMGlobalRuleLeeway;
#pragma mark Server Auth Settings
@@ -318,8 +225,15 @@
+ (instancetype)configurator;
///
/// Clear the sync server configuration from the effective configuration.
/// Designated initializer.
///
- (void)clearSyncState;
/// @param filePath The path to the file to use as a backing store.
///
- (instancetype)initWithFilePath:(NSString *)filePath;
///
/// Re-read config data from disk.
///
- (void)reloadConfigData;
@end

View File

@@ -12,37 +12,49 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTConfigurator.h"
#import "SNTConfigurator.h"
#include <sys/stat.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTSystemInfo.h"
#import "SNTLogging.h"
#import "SNTSystemInfo.h"
@interface SNTConfigurator ()
/// A NSUserDefaults object set to use the com.google.santa suite.
@property(readonly, nonatomic) NSUserDefaults *defaults;
@property NSString *configFilePath;
@property NSMutableDictionary *configData;
// Keys and expected value types.
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
/// Creating NSRegularExpression objects is not fast, so cache them.
@property NSRegularExpression *cachedFileChangesRegex;
@property NSRegularExpression *cachedWhitelistDirRegex;
@property NSRegularExpression *cachedBlacklistDirRegex;
/// Holds the configurations from a sync server and mobileconfig.
@property NSMutableDictionary *syncState;
@property NSMutableDictionary *configState;
/// Array of keys that cannot be changed while santad is running if santad didn't make the change.
@property(readonly) NSArray *protectedKeys;
@end
@implementation SNTConfigurator
/// The hard-coded path to the sync state file.
NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
/// The hard-coded path to the config file
NSString *const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
/// The domain used by mobileconfig.
static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The keys in the config file
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
static NSString *const kEventDetailBundleURLKey = @"EventDetailBundleURL";
static NSString *const kEventDetailTextKey = @"EventDetailText";
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
/// The keys managed by a mobileconfig.
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
@@ -52,100 +64,21 @@ static NSString *const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
static NSString *const kMachineOwnerKey = @"MachineOwner";
static NSString *const kMachineIDKey = @"MachineID";
static NSString *const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
static NSString *const kEventDetailTextKey = @"EventDetailText";
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kFCMFullSyncInterval = @"FCMFullSyncInterval";
static NSString *const kFCMGlobalRuleLeeway = @"FCMGlobalRuleLeeway";
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
static NSString *const kEnableBadSignatureProtectionKey = @"EnableBadSignatureProtection";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters";
static NSString *const kEventLogType = @"EventLogType";
static NSString *const kEventLogPath = @"EventLogPath";
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
static NSString *const kEnableSystemExtension = @"EnableSystemExtension";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kEnableTransitiveWhitelistingKey = @"EnableTransitiveWhitelisting";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
// The keys managed by a sync server.
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (instancetype)init {
- (instancetype)initWithFilePath:(NSString *)filePath {
self = [super init];
if (self) {
Class number = [NSNumber class];
Class re = [NSRegularExpression class];
Class date = [NSDate class];
Class string = [NSString class];
Class data = [NSData class];
Class array = [NSArray class];
_syncServerKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveWhitelistingKey : number,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveWhitelistingKey : number,
kFileChangesRegexKey : re,
kFileChangesPrefixFiltersKey : array,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey: number,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
kUnknownBlockMessage : string,
kBannedBlockMessage : string,
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kSyncBaseURLKey : string,
kClientAuthCertificateFileKey : string,
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
kClientAuthCertificateIssuerKey : string,
kServerAuthRootsDataKey : data,
kServerAuthRootsFileKey : string,
kMachineOwnerKey : string,
kMachineIDKey : string,
kMachineOwnerPlistFileKey : string,
kMachineOwnerPlistKeyKey : string,
kMachineIDPlistFileKey : string,
kMachineIDPlistKeyKey : string,
kEventLogType : string,
kEventLogPath : string,
kEnableMachineIDDecoration : number,
kEnableSystemExtension : number,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
_configState = [self readForcedConfig];
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
[self startWatchingDefaults];
_configFilePath = filePath;
[self reloadConfigData];
}
return self;
}
@@ -153,483 +86,324 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
#pragma mark Singleton retriever
+ (instancetype)configurator {
static SNTConfigurator *sharedConfigurator;
static SNTConfigurator *sharedConfigurator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedConfigurator = [[SNTConfigurator alloc] init];
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
});
return sharedConfigurator;
}
+ (NSSet *)syncAndConfigStateSet {
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [[self syncStateSet] setByAddingObjectsFromSet:[self configStateSet]];
});
return set;
}
#pragma mark Protected Keys
+ (NSSet *)syncStateSet {
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithObject:NSStringFromSelector(@selector(syncState))];
});
return set;
}
+ (NSSet *)configStateSet {
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithObject:NSStringFromSelector(@selector(configState))];
});
return set;
}
#pragma mark KVO Dependencies
+ (NSSet *)keyPathsForValuesAffectingClientMode {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFileChangesRegex {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFileChangesPrefixFiltersKey {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailURL {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailText {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingBannedBlockMessage {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationMonitor {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateFile {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificatePassword {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateCn {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateIssuer {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsData {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsFile {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMachineOwner {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMachineID {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
return [self syncStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingRuleSyncLastSuccess {
return [self syncStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncCleanRequired {
return [self syncStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEventLogType {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEventLogPath {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableTransitiveWhitelisting {
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
return [self configStateSet];
- (NSArray *)protectedKeys {
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey,
kFileChangesRegexKey, kSyncBaseURLKey ];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
SNTClientMode cm = [self.syncState[kClientModeKey] longLongValue];
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
return cm;
NSInteger cm = SNTClientModeUnknown;
id mode = self.configData[kClientModeKey];
if ([mode respondsToSelector:@selector(longLongValue)]) {
cm = (NSInteger)[mode longLongValue];
}
cm = [self.configState[kClientModeKey] longLongValue];
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
return cm;
return (SNTClientMode)cm;
} else {
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
self.clientMode = SNTClientModeMonitor;
return SNTClientModeMonitor;
}
return SNTClientModeMonitor;
}
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
- (void)setClientMode:(SNTClientMode)newMode {
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
self.configData[kClientModeKey] = @(newMode);
[self saveConfigToDisk];
} else {
LOGW(@"Ignoring request to change client mode to %ld", newMode);
}
}
- (BOOL)enableTransitiveWhitelisting {
NSNumber *n = self.syncState[kEnableTransitiveWhitelistingKey];
if (n) {
return [n boolValue];
}
return [self.configState[kEnableTransitiveWhitelistingKey] boolValue];
}
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled {
[self updateSyncStateForKey:kEnableTransitiveWhitelistingKey value:@(enabled)];
}
- (NSRegularExpression *)whitelistPathRegex {
return self.syncState[kWhitelistRegexKey] ?: self.configState[kWhitelistRegexKey];
if (!self.cachedWhitelistDirRegex && self.configData[kWhitelistRegexKey]) {
NSString *re = self.configData[kWhitelistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedWhitelistDirRegex;
}
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kWhitelistRegexKey value:re];
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kWhitelistRegexKey];
} else {
self.configData[kWhitelistRegexKey] = [re pattern];
}
self.cachedWhitelistDirRegex = nil;
[self saveConfigToDisk];
}
- (NSRegularExpression *)blacklistPathRegex {
return self.syncState[kBlacklistRegexKey] ?: self.configState[kBlacklistRegexKey];
if (!self.cachedBlacklistDirRegex && self.configData[kBlacklistRegexKey]) {
NSString *re = self.configData[kBlacklistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedBlacklistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedBlacklistDirRegex;
}
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kBlacklistRegexKey value:re];
- (void)setBlacklistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kBlacklistRegexKey];
} else {
self.configData[kBlacklistRegexKey] = [re pattern];
}
self.cachedBlacklistDirRegex = nil;
[self saveConfigToDisk];
}
- (NSRegularExpression *)fileChangesRegex {
return self.configState[kFileChangesRegexKey];
}
- (NSArray *)fileChangesPrefixFilters {
NSArray *filters = self.configState[kFileChangesPrefixFiltersKey];
for (id filter in filters) {
if (![filter isKindOfClass:[NSString class]]) {
LOGE(@"Ignoring FileChangesPrefixFilters: array contains a non-string %@", filter);
return nil;
}
if (!self.cachedFileChangesRegex && self.configData[kFileChangesRegexKey]) {
NSString *re = self.configData[kFileChangesRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedFileChangesRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return filters;
return self.cachedFileChangesRegex;
}
- (NSURL *)syncBaseURL {
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
NSURL *url = [NSURL URLWithString:urlString];
if (urlString && !url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
- (void)setFileChangesRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kFileChangesRegexKey];
} else {
self.configData[kFileChangesRegexKey] = [re pattern];
}
self.cachedFileChangesRegex = nil;
[self saveConfigToDisk];
}
- (BOOL)enablePageZeroProtection {
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
return number ? [number boolValue] : YES;
}
- (BOOL)enableBadSignatureProtection {
NSNumber *number = self.configState[kEnableBadSignatureProtectionKey];
return number ? [number boolValue] : NO;
NSNumber *keyValue = self.configData[kEnablePageZeroProtectionKey];
return keyValue ? [keyValue boolValue] : YES;
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
return [NSURL URLWithString:self.configData[kMoreInfoURLKey]];
}
- (NSString *)eventDetailURL {
return self.configState[kEventDetailURLKey];
return self.configData[kEventDetailURLKey];
}
- (NSString *)eventDetailBundleURL {
return self.configData[kEventDetailBundleURLKey];
}
- (NSString *)eventDetailText {
return self.configState[kEventDetailTextKey];
return self.configData[kEventDetailTextKey];
}
- (NSString *)unknownBlockMessage {
return self.configState[kUnknownBlockMessage];
return self.configData[kUnknownBlockMessage];
}
- (NSString *)bannedBlockMessage {
return self.configState[kBannedBlockMessage];
return self.configData[kBannedBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configState[kModeNotificationMonitor];
return self.configData[kModeNotificationMonitor];
}
- (NSString *)modeNotificationLockdown {
return self.configState[kModeNotificationLockdown];
return self.configData[kModeNotificationLockdown];
}
- (NSURL *)syncBaseURL {
NSString *urlStr = self.configData[kSyncBaseURLKey];
if (urlStr) {
NSURL *url = [NSURL URLWithString:urlStr];
if (!url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
}
return nil;
}
- (NSString *)syncClientAuthCertificateFile {
return self.configState[kClientAuthCertificateFileKey];
return self.configData[kClientAuthCertificateFileKey];
}
- (NSString *)syncClientAuthCertificatePassword {
return self.configState[kClientAuthCertificatePasswordKey];
return self.configData[kClientAuthCertificatePasswordKey];
}
- (NSString *)syncClientAuthCertificateCn {
return self.configState[kClientAuthCertificateCNKey];
return self.configData[kClientAuthCertificateCNKey];
}
- (NSString *)syncClientAuthCertificateIssuer {
return self.configState[kClientAuthCertificateIssuerKey];
return self.configData[kClientAuthCertificateIssuerKey];
}
- (NSData *)syncServerAuthRootsData {
return self.configState[kServerAuthRootsDataKey];
return self.configData[kServerAuthRootsDataKey];
}
- (NSString *)syncServerAuthRootsFile {
return self.configState[kServerAuthRootsFileKey];
return self.configData[kServerAuthRootsFileKey];
}
- (NSDate *)fullSyncLastSuccess {
return self.syncState[kFullSyncLastSuccess];
return self.configData[kFullSyncLastSuccess];
}
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
[self updateSyncStateForKey:kFullSyncLastSuccess value:fullSyncLastSuccess];
self.configData[kFullSyncLastSuccess] = fullSyncLastSuccess;
[self saveConfigToDisk];
self.ruleSyncLastSuccess = fullSyncLastSuccess;
}
- (NSDate *)ruleSyncLastSuccess {
return self.syncState[kRuleSyncLastSuccess];
return self.configData[kRuleSyncLastSuccess];
}
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
[self updateSyncStateForKey:kRuleSyncLastSuccess value:ruleSyncLastSuccess];
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
[self saveConfigToDisk];
}
- (BOOL)syncCleanRequired {
return [self.syncState[kSyncCleanRequired] boolValue];
return [self.configData[kSyncCleanRequired] boolValue];
}
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
[self updateSyncStateForKey:kSyncCleanRequired value:@(syncCleanRequired)];
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
[self saveConfigToDisk];
}
- (NSString *)machineOwner {
NSString *machineOwner = self.configState[kMachineOwnerKey];
if (machineOwner) return machineOwner;
NSString *machineOwner;
NSString *plistPath = self.configState[kMachineOwnerPlistFileKey];
NSString *plistKey = self.configState[kMachineOwnerPlistKeyKey];
if (plistPath && plistKey) {
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
machineOwner = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
machineOwner = plist[self.configData[kMachineOwnerPlistKeyKey]];
}
return machineOwner ?: @"";
if (self.configData[kMachineOwnerKey]) {
machineOwner = self.configData[kMachineOwnerKey];
}
if (!machineOwner) machineOwner = @"";
return machineOwner;
}
- (NSString *)machineID {
NSString *machineId = self.configState[kMachineIDKey];
if (machineId) return machineId;
NSString *machineId;
NSString *plistPath = self.configState[kMachineIDPlistFileKey];
NSString *plistKey = self.configState[kMachineIDPlistKeyKey];
if (plistPath && plistKey) {
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
machineId = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
machineId = plist[self.configData[kMachineIDPlistKeyKey]];
}
return machineId.length ? machineId : [SNTSystemInfo hardwareUUID];
if (self.configData[kMachineIDKey]) {
machineId = self.configData[kMachineIDKey];
}
if ([machineId length] == 0) {
machineId = [SNTSystemInfo hardwareUUID];
}
return machineId;
}
- (SNTEventLogType)eventLogType {
NSString *s = [self.configState[kEventLogType] lowercaseString];
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
- (NSInteger)FCMFullSyncInterval {
NSInteger interval = [self.configData[kFCMFullSyncInterval] integerValue];
return (interval < 600) ? 1440 : interval;
}
- (NSString *)eventLogPath {
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
- (NSInteger)FCMGlobalRuleLeeway {
NSInteger leeway = [self.configData[kFCMGlobalRuleLeeway] integerValue];
return (leeway < 60) ? 600 : leeway;
}
- (BOOL)enableMachineIDDecoration {
NSNumber *number = self.configState[kEnableMachineIDDecoration];
return number ? [number boolValue] : NO;
}
- (void)reloadConfigData {
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:self.configFilePath]) {
// As soon as saveConfigToDisk is called, reloadConfigData will be called again because
// of the SNTFileWatchers on the config path. No need to use dictionaryWithCapacity: here.
self.configData = [NSMutableDictionary dictionary];
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
[self saveConfigToDisk];
return;
};
- (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;
NSError *error;
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath
options:NSDataReadingMappedIfSafe
error:&error];
if (error) {
LOGE(@"Could not read configuration file: %@, replacing.", [error localizedDescription]);
[self saveConfigToDisk];
return;
}
NSMutableDictionary *configData =
[NSPropertyListSerialization propertyListWithData:readData
options:NSPropertyListMutableContainers
format:NULL
error:&error];
if (error) {
LOGE(@"Could not parse configuration file: %@, replacing.", [error localizedDescription]);
[self saveConfigToDisk];
return;
}
if (self.syncBaseURL) {
// Ensure no-one is trying to change protected keys behind our back.
BOOL changed = NO;
if (geteuid() == 0) {
for (NSString *key in self.protectedKeys) {
if (((self.configData[key] && !configData[key]) ||
(!self.configData[key] && configData[key]) ||
(self.configData[key] && ![self.configData[key] isEqual:configData[key]]))) {
if (self.configData[key]) {
configData[key] = self.configData[key];
} else {
[configData removeObjectForKey:key];
}
changed = YES;
LOGI(@"Ignoring changed configuration key: %@", key);
}
}
}
self.configData = configData;
if (changed) [self saveConfigToDisk];
} else {
return NO;
self.configData = configData;
}
}
#pragma mark Private
///
/// Update the syncState. Triggers a KVO event for all dependents.
/// Saves the current @c self.configData to disk.
///
- (void)updateSyncStateForKey:(NSString *)key value:(id)value {
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[key] = value;
self.syncState = syncState;
[self saveSyncStateToDisk];
});
}
///
/// Read the saved syncState.
///
- (NSMutableDictionary *)readSyncStateFromDisk {
// Only read the sync state if a sync server is configured.
if (!self.syncBaseURL) return nil;
// Only santad should read this file.
if (geteuid() != 0) return nil;
NSMutableDictionary *syncState =
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
for (NSString *key in syncState.allKeys) {
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
syncState[key] = [self expressionForPattern:pattern];
} else if (![syncState[key] isKindOfClass:self.syncServerKeyTypes[key]]) {
syncState[key] = nil;
continue;
}
}
return syncState;
}
///
/// Saves the current effective syncState to disk.
///
- (void)saveSyncStateToDisk {
// Only save the sync state if a sync server is configured.
if (!self.syncBaseURL) return;
// Only santad should write to this file.
- (void)saveConfigToDisk {
if (geteuid() != 0) return;
// Either remove
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[kWhitelistRegexKey] = [syncState[kWhitelistRegexKey] pattern];
syncState[kBlacklistRegexKey] = [syncState[kBlacklistRegexKey] pattern];
[syncState writeToFile:kSyncStateFilePath atomically:YES];
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
ofItemAtPath:kSyncStateFilePath error:NULL];
}
- (void)clearSyncState {
self.syncState = [NSMutableDictionary dictionary];
}
#pragma mark Private Defaults Methods
- (NSRegularExpression *)expressionForPattern:(NSString *)pattern {
if (!pattern) return nil;
if (![pattern hasPrefix:@"^"]) pattern = [@"^" stringByAppendingString:pattern];
return [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
}
- (NSMutableDictionary *)readForcedConfig {
NSMutableDictionary *forcedConfig = [NSMutableDictionary dictionary];
for (NSString *key in self.forcedConfigKeyTypes) {
id obj = [self forcedConfigValueForKey:key];
forcedConfig[key] = [obj isKindOfClass:self.forcedConfigKeyTypes[key]] ? obj : nil;
// Create the regex objects now
if (self.forcedConfigKeyTypes[key] == [NSRegularExpression class]) {
NSString *pattern = [obj isKindOfClass:[NSString class]] ? obj : nil;
forcedConfig[key] = [self expressionForPattern:pattern];
}
}
return forcedConfig;
}
- (id)forcedConfigValueForKey:(NSString *)key {
id obj = [self.defaults objectForKey:key];
return [self.defaults objectIsForcedForKey:key inDomain:kMobileConfigDomain] ? obj : nil;
}
- (void)startWatchingDefaults {
// Only santad should listen.
if (geteuid() != 0) return;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
}
- (void)defaultsChanged:(void *)v {
SEL handleChange = @selector(handleChange);
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:handleChange object:nil];
[self performSelector:handleChange withObject:nil afterDelay:5.0f];
}
///
/// Update the configState. Triggers a KVO event for all dependents.
///
- (void)handleChange {
self.configState = [self readForcedConfig];
[self.configData writeToFile:self.configFilePath atomically:YES];
}
@end

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
///
/// Simple function to check and drop root privileges.

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTDropRootPrivs.h"
#import "SNTDropRootPrivs.h"
BOOL DropRootPrivileges() {
if (getuid() == 0 || geteuid() == 0 || getgid() == 0 || getegid() == 0) {

View File

@@ -12,9 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@class MOLCodesignChecker;
@import Foundation;
///
/// Represents a binary on disk, providing access to details about that binary
@@ -40,18 +38,6 @@
///
- (instancetype)initWithPath:(NSString *)path;
///
/// Initializer for already resolved paths.
///
/// @param path The path of the file this instance is to represent. The path will
/// not be converted and will be used as is. If the path is not a regular file this method will
/// return nil and fill in an error.
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
/// describing the problem.
///
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error;
///
/// @return Path of this file.
///
@@ -125,31 +111,11 @@
///
- (BOOL)isDMG;
///
/// @return NSString describing the kind of file (executable, bundle, script, etc.)
///
- (NSString *)humanReadableFileType;
///
/// @return YES if this file has a bad/missing __PAGEZERO .
///
- (BOOL)isMissingPageZero;
///
/// If set to YES, the bundle* and infoPlist methods will search for and use the highest NSBundle
/// found in the tree. Defaults to NO, which uses the first found bundle, if any.
///
/// @example:
/// An SNTFileInfo object that represents
/// /Applications/Photos.app/Contents/XPCServices/com.apple.Photos.librarychooserservice.xpc
/// useAncestorBundle is set to YES
/// /Applications/Photos.app will be used to get data backing all the bundle methods
///
/// @note: The NSBundle object backing the bundle* and infoPlist methods is cached once found.
/// Setting the useAncestorBundle propery will clear this cache and force a re-search.
///
@property(nonatomic) BOOL useAncestorBundle;
///
/// @return An NSBundle if this file is part of a bundle.
///
@@ -212,16 +178,4 @@
///
- (NSUInteger)fileSize;
///
/// @return The underlying file handle.
///
@property(readonly) NSFileHandle *fileHandle;
///
/// @return Returns an instance of MOLCodeSignChecker initialized with the file's binary path.
/// Both the MOLCodesignChecker and any resulting NSError are cached and returned on subsequent
/// calls. You may pass in NULL for the error if you don't care to receive it.
///
- (MOLCodesignChecker *)codesignCheckerWithError:(NSError **)error;
@end

View File

@@ -12,19 +12,17 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTFileInfo.h"
#import "SNTFileInfo.h"
#import <CommonCrypto/CommonDigest.h>
#import <fmdb/FMDB.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#include <mach-o/arch.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#import <FMDB/FMDB.h>
// Simple class to hold the data of a mach_header and the offset within the file
// in which that header was found.
@@ -55,51 +53,29 @@
@property NSDictionary *infoDict;
@property NSDictionary *quarantineDict;
@property NSDictionary *cachedHeaders;
@property MOLCodesignChecker *cachedCodesignChecker;
@property(nonatomic) NSError *codesignCheckerError;
@end
@implementation SNTFileInfo
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
self = [super init];
if (self) {
_path = path;
if (!_path.length) {
NSBundle *bndl;
_path = [self resolvePath:path bundle:&bndl];
_bundleRef = bndl;
if (_path.length == 0) {
if (error) {
NSString *errStr = @"Unable to use empty path";
NSString *errStr = @"Unable to resolve empty path";
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:270
code:260
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
struct stat fileStat;
lstat(_path.UTF8String, &fileStat);
if (!((S_IFMT & fileStat.st_mode) == S_IFREG)) {
if (error) {
NSString *errStr = [NSString stringWithFormat:@"Non regular file: %s", strerror(errno)];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:290
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
_fileSize = fileStat.st_size;
if (_fileSize == 0) return nil;
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (pwd) {
_fileOwnerHomeDir = @(pwd->pw_dir);
}
}
int fd = open([_path UTF8String], O_RDONLY | O_CLOEXEC);
if (fd < 0) {
if (error) {
@@ -111,26 +87,21 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return nil;
}
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
}
return self;
}
struct stat fileStat;
fstat(_fileHandle.fileDescriptor, &fileStat);
_fileSize = fileStat.st_size;
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
NSBundle *bndl;
NSString *resolvedPath = [self resolvePath:path bundle:&bndl];
if (!resolvedPath.length) {
if (error) {
NSString *errStr = @"Unable to resolve empty path";
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:260
userInfo:@{NSLocalizedDescriptionKey : errStr}];
if (_fileSize == 0) return nil;
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (pwd) {
_fileOwnerHomeDir = @(pwd->pw_dir);
}
}
return nil;
}
self = [self initWithResolvedPath:resolvedPath error:error];
if (self && bndl) _bundleRef = bndl;
return self;
}
@@ -143,71 +114,67 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
const int MAX_CHUNK_SIZE = 256 * 1024; // 256 KB
const size_t chunkSize = _fileSize > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : _fileSize;
char *chunk = malloc(chunkSize);
char chunk[chunkSize];
@try {
CC_SHA1_CTX c1;
CC_SHA256_CTX c256;
CC_SHA1_CTX c1;
CC_SHA256_CTX c256;
if (sha1) CC_SHA1_Init(&c1);
if (sha256) CC_SHA256_Init(&c256);
if (sha1) CC_SHA1_Init(&c1);
if (sha256) CC_SHA256_Init(&c256);
int fd = self.fileHandle.fileDescriptor;
int fd = self.fileHandle.fileDescriptor;
fcntl(fd, F_RDAHEAD, 1);
struct radvisory radv;
radv.ra_offset = 0;
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
fcntl(fd, F_RDADVISE, &radv);
ssize_t bytesRead;
fcntl(fd, F_RDAHEAD, 1);
struct radvisory radv;
radv.ra_offset = 0;
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
fcntl(fd, F_RDADVISE, &radv);
ssize_t bytesRead;
for (uint64_t offset = 0; offset < _fileSize;) {
bytesRead = pread(fd, chunk, chunkSize, offset);
if (bytesRead > 0) {
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
offset += bytesRead;
} else if (bytesRead == -1 && errno == EINTR) {
continue;
} else {
return;
}
for (uint64_t offset = 0; offset < _fileSize;) {
bytesRead = pread(fd, chunk, chunkSize, offset);
if (bytesRead > 0) {
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
offset += bytesRead;
} else if (bytesRead == -1 && errno == EINTR) {
continue;
} else {
return;
}
}
// We turn off Read Ahead that we turned on
fcntl(fd, F_RDAHEAD, 0);
if (sha1) {
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(digest, &c1);
NSString *const SHA1FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha1 = [[NSString alloc]
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19]];
}
if (sha256) {
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &c256);
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
// We turn off Read Ahead that we turned on
fcntl(fd, F_RDAHEAD, 0);
if (sha1) {
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(digest, &c1);
NSString *const SHA1FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha1 = [[NSString alloc]
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19]];
}
if (sha256) {
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &c256);
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha256 = [[NSString alloc]
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19], digest[20],
digest[21], digest[22], digest[23], digest[24],
digest[25], digest[26], digest[27], digest[28],
digest[29], digest[30], digest[31]];
}
} @finally {
free(chunk);
*sha256 = [[NSString alloc]
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19], digest[20],
digest[21], digest[22], digest[23], digest[24],
digest[25], digest[26], digest[27], digest[28],
digest[29], digest[30], digest[31]];
}
}
@@ -270,30 +237,17 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
}
- (BOOL)isDMG {
if (self.fileSize < 512) return NO;
NSUInteger last512 = self.fileSize - 512;
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
return (magic && memcmp("koly", magic, 4) == 0);
}
- (NSString *)humanReadableFileType {
if ([self isExecutable]) return @"Executable";
if ([self isDylib]) return @"Dynamic Library";
if ([self isBundle]) return @"Bundle/Plugin";
if ([self isKext]) return @"Kernel Extension";
if ([self isScript]) return @"Script";
if ([self isXARArchive]) return @"XAR Archive";
if ([self isDMG]) return @"Disk Image";
return @"Unknown";
}
#pragma mark Page Zero
- (BOOL)isMissingPageZero {
// This method only checks i386 arch because the kernel enforces this for other archs
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86
cpuSubType:CPU_SUBTYPE_I386_ALL]];
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86]];
if (!x86Header) return NO;
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
@@ -321,62 +275,39 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
#pragma mark Bundle Information
///
/// Directories with a "Contents/Info.plist" entry can be mistaken as a bundle. To be considered an
/// ancestor, the bundle must have a valid extension.
///
- (NSSet *)allowedAncestorExtensions {
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithArray:@[
@"app",
@"bundle",
@"framework",
@"kext",
@"xctest",
@"xpc",
]];
});
return set;
}
///
/// Try and determine the bundle that the represented executable is contained within, if any.
///
/// Rationale: An NSBundle has a method executablePath for discovering the main binary within a
/// bundle but provides no way to get an NSBundle object when only the executablePath is known.
/// Also a bundle can contain multiple binaries within its subdirectories and we want any of these
/// Also a bundle can contain multiple binaries within the MacOS folder and we want any of these
/// to count as being part of the bundle.
///
/// This method walks up the path until a bundle is found, if any.
/// This method relies on executable bundles being laid out as follows:
///
/// @param ancestor YES this will return the highest NSBundle, with a valid extension, found in the
/// tree. NO will return the the lowest NSBundle, without validating the extension.
/// @code
/// Bundle.app/
/// Contents/
/// MacOS/
/// executable
/// @endcode
///
/// If @c self.path is the full path to @c executable above, this method would return an
/// NSBundle reference for Bundle.app.
///
- (NSBundle *)findBundleWithAncestor:(BOOL)ancestor {
NSBundle *bundle;
NSMutableArray *pathComponents = [[self.path pathComponents] mutableCopy];
// Ignore the root path "/", for some reason this is considered a bundle.
while (pathComponents.count > 1) {
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if ([bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
if (!ancestor ||
[[self allowedAncestorExtensions] containsObject:bndl.bundlePath.pathExtension]) {
bundle = bndl;
}
if (!ancestor) break;
}
[pathComponents removeLastObject];
}
return bundle;
}
- (NSBundle *)bundle {
if (!self.bundleRef) {
self.bundleRef =
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
self.bundleRef = (NSBundle *)[NSNull null];
// Check that the full path is at least 4-levels deep:
// e.g: /Calendar.app/Contents/MacOS/Calendar
NSArray *pathComponents = [self.path pathComponents];
NSUInteger pathComponentsCount = pathComponents.count;
if (pathComponentsCount < 4) return nil;
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponentsCount - 3)];
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = bndl;
}
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
}
@@ -385,14 +316,6 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return [self.bundle bundlePath];
}
- (void)setUseAncestorBundle:(BOOL)useAncestorBundle {
if (self.useAncestorBundle != useAncestorBundle) {
self.bundleRef = nil;
self.infoDict = nil;
}
_useAncestorBundle = useAncestorBundle;
}
- (NSDictionary *)infoPlist {
if (!self.infoDict) {
NSDictionary *d = [self embeddedPlist];
@@ -472,7 +395,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
if (machHeader) {
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
machHeaders[[self nameForCPUType:mh->cputype cpuSubType:mh->cpusubtype]] = mhwo;
machHeaders[[self nameForCPUType:mh->cputype]] = mhwo;
} else {
NSRange range = NSMakeRange(0, sizeof(struct fat_header));
NSData *fatHeader = [self safeSubdataWithRange:range];
@@ -488,12 +411,11 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
int offset = OSSwapBigToHostInt32(fat_arch[i].offset);
int size = OSSwapBigToHostInt32(fat_arch[i].size);
int cputype = OSSwapBigToHostInt(fat_arch[i].cputype);
int cpusubtype = OSSwapBigToHostInt(fat_arch[i].cpusubtype);
range = NSMakeRange(offset, size);
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:range]];
if (machHeader) {
NSString *key = [self nameForCPUType:cputype cpuSubType:cpusubtype];
NSString *key = [self nameForCPUType:cputype];
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader
offset:offset];
machHeaders[key] = mhwo;
@@ -600,7 +522,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
NSData *d = [self.fileHandle readDataOfLength:range.length];
if (d.length != range.length) return nil;
return d;
} @catch (NSException *e) {
}
@catch (NSException *e) {
return nil;
}
}
@@ -677,15 +600,20 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
///
/// Return a human-readable string for a cpu_type_t.
///
- (NSString *)nameForCPUType:(cpu_type_t)cpuType cpuSubType:(cpu_subtype_t)cpuSubType {
const NXArchInfo *archInfo = NXGetArchInfoFromCpuType(cpuType, cpuSubType);
NSString *arch;
if (archInfo && archInfo->name) {
arch = @(archInfo->name);
} else {
arch = [NSString stringWithFormat:@"%i:%i", cpuType, cpuSubType];
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
switch (cpuType) {
case CPU_TYPE_X86:
return @"i386";
case CPU_TYPE_X86_64:
return @"x86-64";
case CPU_TYPE_POWERPC:
return @"ppc";
case CPU_TYPE_POWERPC64:
return @"ppc64";
default:
return @"unknown";
}
return arch;
return nil;
}
///
@@ -710,7 +638,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
BOOL directory;
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
return nil;
} else if (directory && ![path isEqualToString:@"/"]) {
} else if (directory) {
NSBundle *bndl = [NSBundle bundleWithPath:path];
if (bundle) *bundle = bndl;
return [bndl executablePath];
@@ -719,18 +647,4 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
}
}
///
/// Cache and return a MOLCodeSignChecker for the given file. If there was an error creating the
/// code sign checker it will be returned in the passed-in error parameter.
///
- (MOLCodesignChecker *)codesignCheckerWithError:(NSError **)error {
if (!self.cachedCodesignChecker && !self.codesignCheckerError) {
NSError *e;
self.cachedCodesignChecker = [[MOLCodesignChecker alloc] initWithBinaryPath:self.path error:&e];
self.codesignCheckerError = e;
}
if (error) *error = self.codesignCheckerError;
return self.cachedCodesignChecker;
}
@end

View File

@@ -0,0 +1,35 @@
/// 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;
///
/// Simple file watching class using dispatch sources. Will automatically
/// reload the watch if the file is deleted and continue watching for
/// events until deallocated.
///
@interface SNTFileWatcher : NSObject
///
/// Designated initializer
/// Initializes the watcher and begins watching for modifications.
///
/// @param filePath the file to watch.
/// @param handler the handler to call when changes happen. The argument to the block is the
/// type of change that happened as a bitmask to be compared with DISPATCH_VNODE_* constants.
///
- (nonnull instancetype)initWithFilePath:(nonnull NSString *)filePath
handler:(nonnull void (^)(unsigned long))handler;
@end

View File

@@ -0,0 +1,94 @@
/// 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 "SNTFileWatcher.h"
#import "SNTStrengthify.h"
@interface SNTFileWatcher ()
@property NSString *filePath;
@property(copy) void (^handler)(unsigned long);
@property dispatch_source_t source;
@end
@implementation SNTFileWatcher
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (instancetype)initWithFilePath:(nonnull NSString *)filePath
handler:(nonnull void (^)(unsigned long))handler {
self = [super init];
if (self) {
_filePath = filePath;
_handler = handler;
[self startWatchingFile];
}
return self;
}
- (void)dealloc {
[self stopWatchingFile];
}
- (void)startWatchingFile {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME |
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB);
dispatch_async(queue, ^{
int fd = -1;
const char *filePath = [self.filePath fileSystemRepresentation];
while ((fd = open(filePath, O_EVTONLY | O_CLOEXEC)) < 0) {
usleep(200000); // wait 200ms
}
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
WEAKIFY(self);
dispatch_source_set_event_handler(self.source, ^{
STRONGIFY(self);
unsigned long data = dispatch_source_get_data(self.source);
self.handler(data);
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
[self stopWatchingFile];
[self startWatchingFile];
}
sleep(2);
});
dispatch_source_set_registration_handler(self.source, ^{
STRONGIFY(self);
self.handler(0);
});
dispatch_source_set_cancel_handler(self.source, ^{
close(fd);
});
dispatch_resume(self.source);
});
}
- (void)stopWatchingFile {
if (!self.source) return;
dispatch_source_set_event_handler_f(self.source, NULL);
dispatch_source_cancel(self.source);
self.source = nil;
}
@end

View File

@@ -21,6 +21,9 @@
#ifndef SANTA__COMMON__KERNELCOMMON_H
#define SANTA__COMMON__KERNELCOMMON_H
// Defines the lengths of paths and Vnode IDs passed around.
#define MAX_VNODE_ID_STR 21 // digits in UINT64_MAX + 1 for NULL-terminator
// 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"
@@ -33,25 +36,31 @@
enum SantaDriverMethods {
kSantaUserClientOpen,
kSantaUserClientAllowBinary,
kSantaUserClientAllowCompiler,
kSantaUserClientDenyBinary,
kSantaUserClientAcknowledgeBinary,
kSantaUserClientClearCache,
kSantaUserClientRemoveCacheEntry,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
kSantaUserClientCacheBucketCount,
kSantaUserClientFilemodPrefixFilterAdd,
kSantaUserClientFilemodPrefixFilterReset,
kSantaUserClientSetFileopLoggingFilter,
// Any methods supported by the driver should be added above this line to
// ensure this remains the count of methods.
kSantaUserClientNMethods,
};
typedef enum {
kFilterLogNone = 0,
kFilterLogAll = ~0U,
kFileopLogWrite = 1,
kFileopLogRename = 1 << 2,
kFileopLogExchange = 1 << 3,
kFileopLogLink = 1 << 4,
kFileopLogDelete = 1 << 5,
} fileop_log_filter_t;
typedef enum {
QUEUETYPE_DECISION,
QUEUETYPE_LOG,
QUEUETYPE_LOG
} santa_queuetype_t;
// Enum defining actions that can be passed down the IODataQueue and in
@@ -66,12 +75,6 @@ typedef enum {
// RESPONSES
ACTION_RESPOND_ALLOW = 20,
ACTION_RESPOND_DENY = 21,
ACTION_RESPOND_TOOLONG = 22,
ACTION_RESPOND_ACK = 23,
ACTION_RESPOND_ALLOW_COMPILER = 24,
// The following response is stored only in the kernel decision cache.
// It is removed by SNTCompilerController
ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE = 25,
// NOTIFY
ACTION_NOTIFY_EXEC = 30,
@@ -80,39 +83,18 @@ typedef enum {
ACTION_NOTIFY_LINK = 33,
ACTION_NOTIFY_EXCHANGE = 34,
ACTION_NOTIFY_DELETE = 35,
ACTION_NOTIFY_WHITELIST = 36,
// ERROR
ACTION_ERROR = 99,
} santa_action_t;
#define RESPONSE_VALID(x) \
(x == ACTION_RESPOND_ALLOW || \
x == ACTION_RESPOND_DENY || \
x == ACTION_RESPOND_ALLOW_COMPILER || \
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
// Struct to manage vnode IDs
typedef struct santa_vnode_id_t {
uint64_t fsid;
uint64_t fileid;
#ifdef __cplusplus
bool operator==(const santa_vnode_id_t& rhs) const {
return fsid == rhs.fsid && fileid == rhs.fileid;
}
// This _must not_ be used for anything security-sensitive. It exists solely to make
// the msleep/wakeup calls easier.
uint64_t unsafe_simple_id() const {
return (((uint64_t)fsid << 32) | fileid);
}
#endif
} santa_vnode_id_t;
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY)
// Message struct that is sent down the IODataQueue.
typedef struct {
santa_action_t action;
santa_vnode_id_t vnode_id;
uint64_t vnode_id;
uid_t uid;
gid_t gid;
pid_t pid;
@@ -124,18 +106,6 @@ typedef struct {
// While process names can technically be 4*MAXPATHLEN, that never
// actually happens, so only take MAXPATHLEN and throw away any excess.
char pname[MAXPATHLEN];
// For messages that originate from EndpointSecurity, this points to a copy of the message.
void *es_message;
// For messages that originate from EndpointSecurity, this points to an NSArray of the arguments.
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

View File

@@ -24,21 +24,17 @@
#include <IOKit/IOLib.h>
#ifdef DEBUG
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
#else // DEBUG
#define LOGD(format, ...)
#define LOGD(...)
#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__);
#define LOGI(...) IOLog("I santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGW(...) IOLog("W santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGE(...) IOLog("E santa-driver: " __VA_ARGS__); IOLog("\n")
#else // KERNEL
#ifdef __cplusplus
extern "C" {
#endif
#import <Foundation/Foundation.h>
@import Foundation;
typedef enum : NSUInteger {
LOG_LEVEL_ERROR,
@@ -64,10 +60,6 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
#ifdef __cplusplus
} // extern C
#endif
#endif // KERNEL
#endif // SANTA__COMMON__LOGGING_H

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTLogging.h"
#import "SNTLogging.h"
#import <asl.h>
#import <pthread.h>
@@ -24,20 +24,17 @@ static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
#endif
void syslogClientDestructor(void *arg) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
asl_close((aslclient)arg);
#pragma clang diagnostic pop
}
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
static BOOL useSyslog = NO;
static NSString *binaryName;
static const char *binaryName;
static dispatch_once_t pred;
static pthread_key_t syslogKey = 0;
dispatch_once(&pred, ^{
binaryName = [[NSProcessInfo processInfo] processName];
binaryName = [[[NSProcessInfo processInfo] processName] UTF8String];
// If debug logging is enabled, the process must be restarted.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
@@ -45,8 +42,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
}
// If requested, redirect output to syslog.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"]) {
useSyslog = YES;
pthread_key_create(&syslogKey, syslogClientDestructor);
}
@@ -62,11 +58,8 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
if (useSyslog) {
aslclient client = (aslclient)pthread_getspecific(syslogKey);
if (client == NULL) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
client = asl_open(NULL, "com.google.santa", 0);
asl_set_filter(client, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
#pragma clang diagnostic pop
pthread_setspecific(syslogKey, client);
}
@@ -83,7 +76,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
break;
case LOG_LEVEL_INFO:
levelName = "I";
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
syslogLevel = ASL_LEVEL_INFO;
break;
case LOG_LEVEL_DEBUG:
levelName = "D";
@@ -91,10 +84,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
break;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName.UTF8String, s.UTF8String);
#pragma clang diagnostic pop
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
} else {
[s appendString:@"\n"];
size_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];

View File

@@ -1,259 +0,0 @@
/// Copyright 2018 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/common/SNTPrefixTree.h"
#ifdef KERNEL
#include <libkern/locks.h>
#include "Source/common/SNTLogging.h"
#else
#include <mutex>
#include <string.h>
#define LOGD(format, ...) // NOP
#define LOGE(format, ...) // NOP
#define lck_rw_lock_shared(l) pthread_rwlock_rdlock(&l)
#define lck_rw_unlock_shared(l) pthread_rwlock_unlock(&l)
#define lck_rw_lock_exclusive(l) pthread_rwlock_wrlock(&l)
#define lck_rw_unlock_exclusive(l) pthread_rwlock_unlock(&l)
#define lck_rw_lock_shared_to_exclusive(l) ({ pthread_rwlock_unlock(&l); false; })
#define lck_rw_lock_exclusive_to_shared(l) ({ pthread_rwlock_unlock(&l); pthread_rwlock_rdlock(&l); })
#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) {
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could overwrite whole
// branches of another. HasPrefix is still free to read the tree, until AddPrefix needs to
// modify it.
lck_mtx_lock(spt_add_lock_);
// Don't allow an empty prefix.
if (prefix[0] == '\0') return kIOReturnBadArgument;
LOGD("Trying to add prefix: %s", prefix);
// Enforce max tree depth.
size_t len = strnlen(prefix, max_nodes_);
// Grab a shared lock until a new branch is required.
lck_rw_lock_shared(spt_lock_);
SantaPrefixNode *node = root_;
for (int i = 0; i < len; ++i) {
// If there is a node in the path that is considered a prefix, stop adding.
// For our purposes we only care about the shortest path that matches.
if (node->isPrefix) break;
// Only process a byte at a time.
uint8_t value = prefix[i];
// Create the child if it does not exist.
if (!node->children[value]) {
// Upgrade the shared lock.
// If the upgrade fails, the shared lock is released.
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
// Grab a new exclusive lock.
lck_rw_lock_exclusive(spt_lock_);
}
// Is there enough room for the rest of the prefix?
if ((node_count_ + (len - i)) > max_nodes_) {
LOGE("Prefix tree is full, can not add: %s", prefix);
if (node_count) *node_count = node_count_;
lck_rw_unlock_exclusive(spt_lock_);
lck_mtx_unlock(spt_add_lock_);
return kIOReturnNoResources;
}
// Create the rest of the prefix.
while (i < len) {
value = prefix[i++];
SantaPrefixNode *new_node = new SantaPrefixNode();
node->children[value] = new_node;
++node_count_;
node = new_node;
}
// This is the end, mark the node as a prefix.
LOGD("Added prefix: %s", prefix);
node->isPrefix = true;
// Downgrade the exclusive lock
lck_rw_lock_exclusive_to_shared(spt_lock_);
} else if (i + 1 == len) {
// If the child does exist and it is the end...
// Set the new, higher prefix and prune the now dead nodes.
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
lck_rw_lock_exclusive(spt_lock_);
}
PruneNode(node->children[value]);
SantaPrefixNode *new_node = new SantaPrefixNode();
new_node->isPrefix = true;
node->children[value] = new_node;
++node_count_;
LOGD("Added prefix: %s", prefix);
lck_rw_lock_exclusive_to_shared(spt_lock_);
}
// Get ready for the next iteration.
node = node->children[value];
}
if (node_count) *node_count = node_count_;
lck_rw_unlock_shared(spt_lock_);
lck_mtx_unlock(spt_add_lock_);
return kIOReturnSuccess;
}
bool SNTPrefixTree::HasPrefix(const char *string) {
lck_rw_lock_shared(spt_lock_);
auto found = false;
SantaPrefixNode *node = root_;
// A well formed tree will always break this loop. Even if string doesn't terminate.
const char *p = string;
while (*p) {
// Only process a byte at a time.
node = node->children[(uint8_t)*p++];
// If it doesn't exist in the tree, no match.
if (!node) break;
// If it does exist, is it a prefix?
if (node->isPrefix) {
found = true;
break;
}
}
lck_rw_unlock_shared(spt_lock_);
return found;
}
void SNTPrefixTree::Reset() {
lck_rw_lock_exclusive(spt_lock_);
PruneNode(root_);
root_ = new SantaPrefixNode();
node_count_ = 0;
lck_rw_unlock_exclusive(spt_lock_);
}
void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
if (!target) return;
// For deep trees, a recursive approach will generate too many stack frames. Make a "stack"
// and walk the tree.
auto stack = new SantaPrefixNode *[node_count_ + 1];
if (!stack) {
LOGE("Unable to prune tree!");
return;
}
auto count = 0;
// Seed the "stack" with a starting node.
stack[count++] = target;
// Start at the target node and walk the tree to find and delete all the sub-nodes.
while (count) {
auto node = stack[--count];
for (int i = 0; i < 256; ++i) {
if (!node->children[i]) continue;
stack[count++] = node->children[i];
}
delete node;
--node_count_;
}
delete[] stack;
}
SNTPrefixTree::~SNTPrefixTree() {
lck_rw_lock_exclusive(spt_lock_);
PruneNode(root_);
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

@@ -1,103 +0,0 @@
/// Copyright 2018 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__SANTAPREFIXTREE_H
#define SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H
#include <IOKit/IOReturn.h>
#include <sys/param.h>
#ifdef KERNEL
#include <libkern/locks.h>
#else
// Support for unit testing.
#include <mutex>
#include <pthread.h>
#include <stdint.h>
#endif // KERNEL
///
/// SantaPrefixTree is a simple prefix tree implementation.
/// Operations are thread safe.
///
class SNTPrefixTree {
public:
// Add a prefix to the tree.
// Optionally pass node_count to get the number of nodes after the add.
IOReturn AddPrefix(const char *, uint64_t *node_count = nullptr);
// Check if the tree has a prefix for string.
bool HasPrefix(const char *string);
// Reset the tree.
void Reset();
SNTPrefixTree(uint32_t max_nodes = kDefaultMaxNodes);
~SNTPrefixTree();
private:
///
/// SantaPrefixNode is a wrapper class that represents one byte.
/// 1 node can represent a whole ASCII character.
/// For example a pointer to the 'A' node will be stored at children[0x41].
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
///
/// The path for "/🤘" would look like this:
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4] -> children[0x98]
///
/// The path for "/dev" is:
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
///
/// Lookups of children are O(1).
///
/// Having the nodes represented by a smaller width, such as a nibble (1/2 byte), would
/// drastically decrease the memory footprint but would double required dereferences.
///
/// TODO(bur): Potentially convert this into a full on radix tree.
///
class SantaPrefixNode {
public:
bool isPrefix;
SantaPrefixNode *children[256];
};
// PruneNode will remove the passed in node from the tree.
// The passed in node and all subnodes will be deleted.
// It is the caller's responsibility to reset the pointer to this node (held by the parent).
// If the tree is in use grab the exclusive lock.
void PruneNode(SantaPrefixNode *);
SantaPrefixNode *root_;
// Each node takes up ~2k, assuming MAXPATHLEN is 1024 max out at ~2MB.
static const uint32_t kDefaultMaxNodes = MAXPATHLEN;
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
void *spt_lock_grp_;
void *spt_lock_grp_attr_;
void *spt_lock_attr_;
pthread_rwlock_t spt_lock_;
std::mutex *spt_add_lock_;
#endif // KERNEL
};
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */

View File

@@ -1,70 +0,0 @@
/// Copyright 2018 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 <XCTest/XCTest.h>
#include "Source/common/SNTPrefixTree.h"
@interface SNTPrefixTreeTest : XCTestCase
@end
@implementation SNTPrefixTreeTest
- (void)testAddAndHas {
auto t = SNTPrefixTree();
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
t.AddPrefix("/private/var/tmp/");
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
}
- (void)testReset {
auto t = SNTPrefixTree();
t.AddPrefix("/private/var/tmp/");
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
t.Reset();
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
}
- (void)testThreading {
uint32_t count = 4096;
auto t = new SNTPrefixTree(count * (uint32_t)[NSUUID UUID].UUIDString.length);
NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count; ++i) {
[UUIDs addObject:[NSUUID UUID].UUIDString];
}
// Create a bunch of background noise.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_apply(UINT64_MAX, dispatch_get_global_queue(0, 0), ^(size_t i) {
t->HasPrefix([UUIDs[i % count] UTF8String]);
});
});
// Fill up the tree.
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
if (t->AddPrefix([UUIDs[i] UTF8String]) != kIOReturnSuccess) {
XCTFail();
}
});
// Make sure every leaf byte is found.
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
if (!t->HasPrefix([UUIDs[i] UTF8String])) {
XCTFail();
}
});
}
@end

View File

@@ -12,9 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
#import "Source/common/SNTCommonEnums.h"
#import "SNTCommonEnums.h"
///
/// Represents a Rule.
@@ -41,32 +41,12 @@
///
@property(copy) NSString *customMsg;
///
/// The time when this rule was last retrieved from the rules database, if rule is transitive.
/// Stored as number of seconds since 00:00:00 UTC on 1 January 2001.
///
@property(readonly) NSUInteger timestamp;
///
/// Designated initializer.
///
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp;
///
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
///
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;
///
/// Sets timestamp of rule to the current time.
///
- (void)resetTimestamp;
@end

View File

@@ -12,51 +12,26 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTRule.h"
@interface SNTRule()
@property(readwrite) NSUInteger timestamp;
@end
#import "SNTRule.h"
@implementation SNTRule
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
customMsg:(NSString *)customMsg {
self = [super init];
if (self) {
_shasum = shasum;
_state = state;
_type = type;
_customMsg = customMsg;
_timestamp = timestamp;
}
return self;
}
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithShasum:shasum
state:state
type:type
customMsg:customMsg
timestamp:0];
// Initialize timestamp to current time if rule is transitive.
if (self && state == SNTRuleStateWhitelistTransitive) {
[self resetTimestamp];
}
return self;
}
#pragma mark NSSecureCoding
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
@@ -69,7 +44,6 @@
ENCODE(@(self.state), @"state");
ENCODE(@(self.type), @"type");
ENCODE(self.customMsg, @"custommsg");
ENCODE(@(self.timestamp), @"timestamp");
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
@@ -79,14 +53,12 @@
_state = [DECODE(NSNumber, @"state") intValue];
_type = [DECODE(NSNumber, @"type") intValue];
_customMsg = DECODE(NSString, @"custommsg");
_timestamp = [DECODE(NSNumber, @"timestamp") unsignedIntegerValue];
}
return self;
}
#undef DECODE
#undef ENCODE
#pragma clang diagnostic pop
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
@@ -105,14 +77,8 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
}
# pragma mark Last-access Timestamp
- (void)resetTimestamp {
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
self.shasum, self.state, self.type];
}
@end

View File

@@ -12,9 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
#import "Source/common/SNTCommonEnums.h"
#import "SNTCommonEnums.h"
///
/// Represents an event stored in the database.
@@ -36,28 +36,6 @@
///
@property NSString *filePath;
///
/// Set to YES if the event is a part of a bundle. When an event is passed to SantaGUI this propery
/// will be used as an indicator to to kick off bundle hashing as necessary. Default value is NO.
///
@property BOOL needsBundleHash;
///
/// If the executed file was part of a bundle, this is the calculated hash of all the nested
/// executables within the bundle.
///
@property NSString *fileBundleHash;
///
/// If the executed file was part of a bundle, this is the time in ms it took to hash the bundle.
///
@property NSNumber *fileBundleHashMilliseconds;
///
/// If the executed file was part of a bundle, this is the total count of related mach-o binaries.
///
@property NSNumber *fileBundleBinaryCount;
///
/// If the executed file was part of the bundle, this is the CFBundleDisplayName, if it exists
/// or the CFBundleName if not.
@@ -69,11 +47,6 @@
///
@property NSString *fileBundlePath;
///
/// The relative path to the bundle's main executable.
///
@property NSString *fileBundleExecutableRelPath;
///
/// If the executed file was part of the bundle, this is the CFBundleID.
///

View File

@@ -12,15 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTStoredEvent.h"
#import "SNTStoredEvent.h"
#import <MOLCertificate/MOLCertificate.h>
#import "MOLCertificate.h"
@implementation SNTStoredEvent
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
#define DECODEARRAY(cls, key) \
@@ -36,13 +33,8 @@
ENCODE(self.fileSHA256, @"fileSHA256");
ENCODE(self.filePath, @"filePath");
ENCODE(@(self.needsBundleHash), @"needsBundleHash");
ENCODE(self.fileBundleHash, @"fileBundleHash");
ENCODE(self.fileBundleHashMilliseconds, @"fileBundleHashMilliseconds");
ENCODE(self.fileBundleBinaryCount, @"fileBundleBinaryCount");
ENCODE(self.fileBundleName, @"fileBundleName");
ENCODE(self.fileBundlePath, @"fileBundlePath");
ENCODE(self.fileBundleExecutableRelPath, @"fileBundleExecutableRelPath");
ENCODE(self.fileBundleID, @"fileBundleID");
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
@@ -80,13 +72,8 @@
_fileSHA256 = DECODE(NSString, @"fileSHA256");
_filePath = DECODE(NSString, @"filePath");
_needsBundleHash = [DECODE(NSNumber, @"needsBundleHash") boolValue];
_fileBundleHash = DECODE(NSString, @"fileBundleHash");
_fileBundleHashMilliseconds = DECODE(NSNumber, @"fileBundleHashMilliseconds");
_fileBundleBinaryCount = DECODE(NSNumber, @"fileBundleBinaryCount");
_fileBundleName = DECODE(NSString, @"fileBundleName");
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
_fileBundleExecutableRelPath = DECODE(NSString, @"fileBundleExecutableRelPath");
_fileBundleID = DECODE(NSString, @"fileBundleID");
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
@@ -132,6 +119,4 @@
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
}
#pragma clang diagnostic pop
@end

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
///
/// Simple class for fetching system information

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTSystemInfo.h"
#import "SNTSystemInfo.h"
@implementation SNTSystemInfo

View File

@@ -1,70 +0,0 @@
/// Copyright 2017 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 <MOLXPCConnection/MOLXPCConnection.h>
@class SNTStoredEvent;
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
@protocol SNTBundleServiceXPC
///
/// @param listener The listener to connect back to the SantaGUI.
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Hash a bundle for an event. The SNTBundleHashBlock will be called with nil parameters if a
/// failure or cancellation occurs.
///
/// @param event The event that includes the fileBundlePath to be hashed. This method will
/// attempt to to find and use the ancestor bundle as a starting point.
/// @param reply A SNTBundleHashBlock to be executed upon completion or cancellation.
///
/// @note If there is a current NSProgress when called this method will report back its progress.
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
///
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice know you are done with it.
///
- (void)spindown;
@end
@interface SNTXPCBundleServiceInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTBundleServiceXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
///
+ (NSXPCInterface *)bundleServiceInterface;
///
/// Returns the MachService ID for this service.
///
+ (NSString *)serviceID;
///
/// Retrieve a pre-configured MOLXPCConnection for communicating with santabundleservice.
/// Connections just needs any handlers set and then can be resumed and used.
///
+ (MOLXPCConnection *)configuredConnection;
@end

View File

@@ -1,43 +0,0 @@
/// Copyright 2017 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/SNTXPCBundleServiceInterface.h"
#import "Source/common/SNTStoredEvent.h"
@implementation SNTXPCBundleServiceInterface
+ (NSXPCInterface *)bundleServiceInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(hashBundleBinariesForEvent:reply:)
argumentIndex:1
ofReply:YES];
return r;
}
+ (NSString *)serviceID {
return @"com.google.santa.bundleservice";
}
+ (MOLXPCConnection *)configuredConnection {
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
privileged:YES];
c.remoteInterface = [self bundleServiceInterface];
return c;
}
@end

View File

@@ -0,0 +1,128 @@
/// 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;
/**
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
validation of connecting clients and forced connection establishment.
Example server started by @c launchd where the @c launchd job has a @c MachServices key:
@code
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
conn.exportedObject = myObject;
[conn resume];
@endcode
Example client, connecting to above server:
@code
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer"
withOptions:0];
conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
[conn resume];
@endcode
The client can send a message to the server with:
@code
[conn.remoteObjectProxy selectorInRemoteInterface];
@endcode
One advantage of the way that SNTXPCConnection works over using NSXPCConnection directly is that
from the client-side once the resume method has finished, the connection is either valid or the
invalidation handler will be called. Ordinarily, the connection doesn't actually get made until
the first message is sent across it.
@note messages are always delivered on a background thread!
*/
@interface SNTXPCConnection : NSObject<NSXPCListenerDelegate>
/**
Initialize a new server with a given listener, provided by `[NSXPCListener anonymousListener]`.
*/
- (nullable instancetype)initServerWithListener:(nonnull NSXPCListener *)listener;
/**
Initializer for the 'server' side of the connection, started by launchd.
@param name MachService name, must match the MachServices key in the launchd.plist
*/
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
/**
Initializer a new client to a service exported by a LaunchDaemon.
@param name MachService name
@param privileged Use YES if the server is running as root.
*/
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
/**
Initialize a new client with a listener endpoint sent from another process.
@param listener An NSXPCListenerEndpoint to connect to.
*/
- (nullable instancetype)initClientWithListener:(nonnull NSXPCListenerEndpoint *)listener;
/**
Call when the properties of the object have been set-up and you're ready for connections.
For clients, this call can take up to 2s to complete for connection to finish establishing though
in basically all cases it will actually complete in a few milliseconds.
*/
- (void)resume;
/**
Invalidate the connection(s). This must be done before the object can be released.
*/
- (void)invalidate;
/**
The interface the remote object should conform to. (client)
*/
@property(retain, nullable) NSXPCInterface *remoteInterface;
/**
A proxy to the object at the other end of the connection. (client)
@note If the connection to the server failed, this will be nil, so you can safely send messages
and rely on the invalidationHandler for handling the failure.
*/
@property(readonly, nonatomic, nullable) id remoteObjectProxy;
/**
The interface this object exports. (server)
*/
@property(retain, nullable) NSXPCInterface *exportedInterface;
/**
The object that responds to messages from the other end. (server)
*/
@property(retain, nullable) id exportedObject;
/**
A block to run when a/the connection is accepted and fully established.
*/
@property(copy, nullable) void (^acceptedHandler)(void);
/**
A block to run when a/the connection is invalidated/interrupted/rejected.
*/
@property(copy, nullable) void (^invalidationHandler)(void);
@end

View File

@@ -0,0 +1,193 @@
/// 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 "SNTXPCConnection.h"
#import "MOLCodesignChecker.h"
#import "SNTStrengthify.h"
/**
Protocol used during connection establishment, @see SNTXPCConnectionInterface
*/
@protocol SNTXPCConnectionProtocol
- (void)connectWithReply:(void (^)())reply;
@end
/**
Recipient object used during connection establishment. Each incoming connection
has one of these objects created which accept the message in the protocol
and call the block provided during creation before replying.
This allows the server to reset the connection's exported interface and
object to the correct values after the client has sent the establishment message.
*/
@interface SNTXPCConnectionInterface : NSObject<SNTXPCConnectionProtocol>
@property(strong) void (^block)(void);
@end
@implementation SNTXPCConnectionInterface
- (void)connectWithReply:(void (^)())reply {
if (self.block) self.block();
reply();
}
@end
@interface SNTXPCConnection ()
@property NSXPCInterface *validationInterface;
/// The XPC listener (server only).
@property NSXPCListener *listenerObject;
/// The current connection object (client only).
@property NSXPCConnection *currentConnection;
@end
@implementation SNTXPCConnection
#pragma mark Initializers
- (instancetype)initServerWithListener:(NSXPCListener *)listener {
self = [super init];
if (self) {
_listenerObject = listener;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
- (instancetype)initServerWithName:(NSString *)name {
return [self initServerWithListener:[[NSXPCListener alloc] initWithMachServiceName:name]];
}
- (instancetype)initClientWithListener:(NSXPCListenerEndpoint *)listener {
self = [super init];
if (self) {
_currentConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:listener];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
- (instancetype)initClientWithName:(NSString *)name privileged:(BOOL)privileged {
self = [super init];
if (self) {
NSXPCConnectionOptions options = (privileged ? NSXPCConnectionPrivileged : 0);
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name options:options];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
#pragma mark Connection set-up
- (void)resume {
if (self.listenerObject) {
self.listenerObject.delegate = self;
[self.listenerObject resume];
} else {
WEAKIFY(self);
// Set-up the connection with the remote interface set to the validation interface,
// send a message to the listener to finish establishing the connection
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
self.currentConnection.remoteObjectInterface = self.validationInterface;
self.currentConnection.interruptionHandler = self.currentConnection.invalidationHandler = ^{
STRONGIFY(self);
if (self.invalidationHandler) self.invalidationHandler();
};
[self.currentConnection resume];
[[self.currentConnection remoteObjectProxy] connectWithReply:^{
STRONGIFY(self);
// The connection is now established
[self.currentConnection suspend];
self.currentConnection.remoteObjectInterface = self.remoteInterface;
[self.currentConnection resume];
dispatch_semaphore_signal(sema);
if (self.acceptedHandler) self.acceptedHandler();
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
// Connection was not established in a reasonable time, invalidate.
self.currentConnection.remoteObjectInterface = nil; // ensure clients don't try to use it.
[self.currentConnection invalidate];
}
}
}
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection {
pid_t pid = connection.processIdentifier;
MOLCodesignChecker *otherCS = [[MOLCodesignChecker alloc] initWithPID:pid];
if (![otherCS signingInformationMatches:[[MOLCodesignChecker alloc] initWithSelf]]) {
return NO;
}
// The client passed the code signature check, now we need to resume the listener and
// return YES so that the client can send the connectWithReply message. Once the client does
// we reset the connection's exportedInterface and exportedObject.
SNTXPCConnectionInterface *ci = [[SNTXPCConnectionInterface alloc] init];
WEAKIFY(self);
WEAKIFY(connection);
ci.block = ^{
STRONGIFY(self)
STRONGIFY(connection);
[connection suspend];
connection.invalidationHandler = connection.interruptionHandler = ^{
if (self.invalidationHandler) self.invalidationHandler();
};
connection.exportedInterface = self.exportedInterface;
connection.exportedObject = self.exportedObject;
[connection resume];
// The connection is now established.
if (self.acceptedHandler) self.acceptedHandler();
};
connection.exportedInterface = self.validationInterface;
connection.exportedObject = ci;
[connection resume];
return YES;
}
- (id)remoteObjectProxy {
if (self.currentConnection.remoteObjectInterface &&
self.currentConnection.remoteObjectInterface != self.validationInterface) {
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
[self.currentConnection invalidate];
}];
}
return nil;
}
#pragma mark Connection tear-down
- (void)invalidate {
if (self.currentConnection) {
[self.currentConnection invalidate];
self.currentConnection = nil;
} else if (self.listenerObject) {
[self.listenerObject invalidate];
}
}
@end

View File

@@ -12,48 +12,87 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
@import Foundation;
#import <MOLCertificate/MOLCertificate.h>
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
@class SNTRule;
@class SNTStoredEvent;
@class SNTXPCConnection;
///
/// Protocol implemented by santad and utilized by santactl (privileged operations)
/// Protocol implemented by santad and utilized by santactl
///
@protocol SNTDaemonControlXPC <SNTUnprivilegedDaemonControlXPC>
@protocol SNTDaemonControlXPC
///
/// Kernel ops
///
- (void)cacheCount:(void (^)(int64_t))reply;
- (void)flushCache:(void (^)(BOOL))reply;
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply;
- (void)setFileopLoggingFilter:(fileop_log_filter_t)filter withReply:(void (^)())reply;
///
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(NSError *error))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTRule *))reply;
///
/// Decision ops
///
///
/// @param filePath A Path to the file, can be nil.
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
/// be calculated by this method from the filePath.
/// @param signingCertificate A MOLCertificate object, can be nil.
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
/// returned. Binary rules take precedence over cert rules.
///
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
signingCertificate:(MOLCertificate *)signingCertificate
reply:(void (^)(SNTEventState))reply;
///
/// Config ops
///
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)(void))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)(void))reply;
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
- (void)setEnableTransitiveWhitelisting:(BOOL)enabled reply:(void (^)(void))reply;
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;
///
/// GUI Ops
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)pushNotifications:(void (^)(BOOL))reply;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply;
@end
@@ -62,23 +101,18 @@
///
/// Returns the MachService ID for this service.
///
+ (NSString *)serviceID;
+ (NSString *)serviceId;
///
/// Returns the SystemExtension ID for this service.
///
+ (NSString *)systemExtensionID;
///
/// Returns an initialized NSXPCInterface for the SNTUnprivilegedDaemonControlXPC protocol.
/// Returns an initialized NSXPCInterface for the SNTDaemonControlXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)controlInterface;
///
/// Retrieve a pre-configured MOLXPCConnection for communicating with santad.
/// Retrieve a pre-configured SNTXPCConnection for communicating with santad.
/// Connections just needs any handlers set and then can be resumed and used.
///
+ (MOLXPCConnection *)configuredConnection;
+ (SNTXPCConnection *)configuredConnection;
@end

View File

@@ -12,35 +12,21 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCControlInterface.h"
#import "SNTXPCControlInterface.h"
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
NSString *const kBundleID = @"com.google.santa.daemon";
#import "SNTRule.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
@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;
+ (NSString *)serviceId {
return @"SantaXPCControl";
}
+ (NSString *)systemExtensionID {
return kBundleID;
}
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
+ (void)initializeControlInterface:(NSXPCInterface *)r {
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(databaseEventsPending:)
argumentIndex:0
@@ -50,17 +36,12 @@ NSString *const kBundleID = @"com.google.santa.daemon";
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
argumentIndex:0
ofReply:NO];
}
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
[self initializeControlInterface:r];
return r;
}
+ (MOLXPCConnection *)configuredConnection {
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
+ (SNTXPCConnection *)configuredConnection {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithName:[self serviceId]
privileged:YES];
c.remoteInterface = [self controlInterface];
return c;

View File

@@ -12,10 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTXPCBundleServiceInterface.h"
#import "SNTCommonEnums.h"
@class SNTStoredEvent;
@@ -24,10 +23,6 @@
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
- (void)postClientModeNotification:(SNTClientMode)clientmode;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount;
@end
@interface SNTXPCNotifierInterface : NSObject

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCNotifierInterface.h"
#import "SNTXPCNotifierInterface.h"
@implementation SNTXPCNotifierInterface

View File

@@ -12,17 +12,16 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
@import Foundation;
#import "Source/common/SNTCommonEnums.h"
#import "SNTCommonEnums.h"
@class SNTStoredEvent;
/// Protocol implemented by santactl and utilized by santad
@protocol SNTSyncdXPC
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply;
- (void)postEventToSyncServer:(SNTStoredEvent *)event;
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end

View File

@@ -12,21 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTStoredEvent.h"
#import "SNTXPCSyncdInterface.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
argumentIndex:0
ofReply:NO];
return r;
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
}
@end

View File

@@ -1,106 +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 <MOLCertificate/MOLCertificate.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
@class SNTRule;
@class SNTStoredEvent;
@class MOLXPCConnection;
///
/// Protocol implemented by santad and utilized by santactl (unprivileged operations)
///
@protocol SNTUnprivilegedDaemonControlXPC
///
/// Kernel ops
///
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
///
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary,
int64_t certificate,
int64_t compiler,
int64_t transitive))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
///
/// Decision ops
///
///
/// @param filePath A Path to the file, can be nil.
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
/// be calculated by this method from the filePath.
/// @param certificateSHA256 A SHA256 hash of the signing certificate, can be nil.
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
/// returned. Binary rules take precedence over cert rules.
///
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply;
///
/// Config ops
///
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)syncCleanRequired:(void (^)(BOOL))reply;
- (void)enableBundles:(void (^)(BOOL))reply;
- (void)enableTransitiveWhitelisting:(void (^)(BOOL))reply;
///
/// GUI Ops
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)pushNotifications:(void (^)(BOOL))reply;
///
/// Bundle Ops
///
- (void)syncBundleEvent:(SNTStoredEvent *)event relatedEvents:(NSArray<SNTStoredEvent *> *)events;
@end
@interface SNTXPCUnprivilegedControlInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTUnprivilegedDaemonControlXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)controlInterface;
///
/// Internal method used to initialize the control interface
///
+ (void)initializeControlInterface:(NSXPCInterface *)r;
@end

View File

@@ -1,38 +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/common/SNTXPCUnprivilegedControlInterface.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
@implementation SNTXPCUnprivilegedControlInterface
+ (void)initializeControlInterface:(NSXPCInterface *)r {
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(syncBundleEvent:relatedEvents:)
argumentIndex:1
ofReply:NO];
}
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
[self initializeControlInterface:r];
return r;
}
@end

Some files were not shown because too many files have changed in this diff Show More