mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0898940d0b | ||
|
|
38b65b0ca4 | ||
|
|
d36ce5eefc | ||
|
|
ff99ab9cfe | ||
|
|
64995367c3 | ||
|
|
c67f0ffc11 | ||
|
|
d5403ae112 | ||
|
|
d21d64cbfe | ||
|
|
347ee3c4f5 | ||
|
|
77ed1cca29 | ||
|
|
cfac7dbb37 | ||
|
|
f27d72f3f9 | ||
|
|
3cd93b287e | ||
|
|
5e5605881b | ||
|
|
a9b48610df | ||
|
|
3cca09a48c | ||
|
|
3134448eac | ||
|
|
663bdf945b | ||
|
|
e94d1175e7 | ||
|
|
e20b761965 | ||
|
|
90c64812d0 | ||
|
|
08d368fc49 | ||
|
|
39385f0bff | ||
|
|
8bc3418ce1 | ||
|
|
a145700398 | ||
|
|
409535e617 | ||
|
|
f625016efe | ||
|
|
f4c94ab1d7 | ||
|
|
8234706dd3 | ||
|
|
1a31dc870f | ||
|
|
a1712858c5 | ||
|
|
0059e768b9 | ||
|
|
4fe1550bd2 | ||
|
|
0c182c8a7f | ||
|
|
bcdf746def | ||
|
|
bc13ac3a98 | ||
|
|
a894e018cd | ||
|
|
cbecfd444d | ||
|
|
357e5ef963 | ||
|
|
60594c9f03 | ||
|
|
44b5bae8da | ||
|
|
2e856196c5 | ||
|
|
8672187c02 | ||
|
|
cf251c45b8 | ||
|
|
385c03096d | ||
|
|
f323f5e3de | ||
|
|
9562ee86cd | ||
|
|
adfb4bc861 | ||
|
|
957232ca40 | ||
|
|
44c9d9aead | ||
|
|
f95245cedd | ||
|
|
3c034adf48 | ||
|
|
abd3c5a06d | ||
|
|
ca4951a475 | ||
|
|
e751a3d307 | ||
|
|
2a8bdfd714 | ||
|
|
be9dca3ee2 | ||
|
|
32707fb501 | ||
|
|
d72547e187 | ||
|
|
9150ddffb1 |
@@ -13,70 +13,52 @@ TITLE:=santa
|
||||
REVERSE_DOMAIN:=com.google
|
||||
|
||||
# Get latest Release version using the GitHub API. Each release is bound to a
|
||||
# git tag, which should always be a semantic version number
|
||||
# git tag, which should always be a semantic version number. The most recent
|
||||
# release is always first in the API result.
|
||||
PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
|
||||
python -c 'import json, sys; print json.load(sys.stdin)[0]["tag_name"]' 2>/dev/null)
|
||||
|
||||
# Get the download URL for the latest Release using the GitHub API. Each release
|
||||
# should have a single 'asset' which will be a tar.bz2 containing all of the files
|
||||
# associated with that release.
|
||||
# The tarball layout is:
|
||||
# Get the download URL for the latest Release. Each release should have a
|
||||
# tarball named santa-$version.tar.bz2 containing all of the files associated
|
||||
# with that release. The tarball layout is:
|
||||
#
|
||||
# santa-$version.tar.bz2
|
||||
# +--santa-$version
|
||||
# |-- binaries
|
||||
# | |-- santa-driver.kext
|
||||
# | |-- Santa.app
|
||||
# | |-- santad
|
||||
# | +-- santactl
|
||||
# | |-- santa-driver.kext
|
||||
# | |-- Santa.app
|
||||
# |-- conf
|
||||
# | |-- install.sh
|
||||
# | |-- com.google.santad.plist
|
||||
# | |-- com.google.santasync.plist
|
||||
# | |-- com.google.santagui.plist
|
||||
# | +-- com.google.santa.asl.conf
|
||||
# +--dsym
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
# |-- santad.dSYM
|
||||
# +-- santactl.dSYM
|
||||
#
|
||||
PACKAGE_DOWNLOAD_URL:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
|
||||
python -c 'import json, sys; print json.load(sys.stdin)[0]["assets"][0]["browser_download_url"]' 2>/dev/null)
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
# |-- santad.dSYM
|
||||
# +-- santactl.dSYM
|
||||
PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKAGE_VERSION}/santa-${PACKAGE_VERSION}.tar.bz2"
|
||||
|
||||
PAYLOAD:=pack-usr-libexec-santad \
|
||||
pack-usr-sbin-santactl \
|
||||
pack-Library-Extensions-santa-driver.kext \
|
||||
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
|
||||
pack-applications-Santa.app \
|
||||
pack-Library-LaunchDaemons-com.google.santad.plist \
|
||||
pack-Library-LaunchDaemons-com.google.santasync.plist \
|
||||
pack-Library-LaunchAgents-com.google.santagui.plist \
|
||||
pack-etc-asl-com.google.santa.asl.conf \
|
||||
pack-script-preinstall \
|
||||
pack-script-postinstall
|
||||
|
||||
santad: download
|
||||
santactl: download
|
||||
santa-driver.kext: download
|
||||
Santa.app: download
|
||||
com.google.santad.plist: download
|
||||
com.google.santagui.plist: download
|
||||
com.google.santasync.plist: download
|
||||
com.google.santa.asl.conf: download
|
||||
|
||||
download:
|
||||
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
$(if $(PACKAGE_DOWNLOAD_URL),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
|
||||
@curl-fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
|
||||
@curl -fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
|
||||
@rm -rf *.dSYM
|
||||
|
||||
pack-usr-libexec-santad: santad l_usr
|
||||
@sudo mkdir -p ${WORK_D}/usr/libexec
|
||||
@sudo chown root:wheel ${WORK_D}/usr/libexec
|
||||
@sudo chmod 755 ${WORK_D}/usr/libexec
|
||||
@sudo install -m 755 -o root -g wheel santad ${WORK_D}/usr/libexec
|
||||
|
||||
pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/asl
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/asl
|
||||
@@ -95,11 +77,8 @@ myclean:
|
||||
@rm -rf *.dSYM
|
||||
@rm -rf Santa.app
|
||||
@rm -rf santa-driver.kext
|
||||
@rm -f santad
|
||||
@rm -f santactl
|
||||
@rm -f config.plist
|
||||
@rm -f com.google.santa.asl.conf
|
||||
@rm -f com.google.santad.plist
|
||||
@rm -f com.google.santagui.plist
|
||||
@rm -f com.google.santasync.plist
|
||||
@rm -f install.sh
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
sleep 1
|
||||
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santasync.plist
|
||||
|
||||
sleep 1
|
||||
|
||||
# Create hopefully useful symlink for santactl
|
||||
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "$user" ]] && exit 0
|
||||
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
|
||||
|
||||
@@ -7,12 +7,17 @@
|
||||
[[ $3 != "/" ]] && exit 0
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
/bin/launchctl remove com.google.santasync
|
||||
|
||||
sleep 1
|
||||
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Remove cruft from old Santa versions
|
||||
/bin/rm /usr/libexec/santad
|
||||
/bin/rm /usr/sbin/santactl
|
||||
/bin/launchctl remove com.google.santasync
|
||||
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist
|
||||
|
||||
sleep 1
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
|
||||
? [S= Message santa-driver:] claim only
|
||||
? [S= Message santa-driver:] file /var/log/santa.log format="[$((Time)(utc.3))] $Message"
|
||||
> /var/log/santa.log mode=0644 rotate=seq compress file_max=5M all_max=100M
|
||||
> /var/log/santa.log format="[$((Time)(utc.3))] $Message" mode=0644 rotate=seq compress file_max=10M all_max=100M uid=0 gid=0
|
||||
? [S= Message santa-driver:] claim
|
||||
? [S= Message santa-driver:] file /var/log/santa.log
|
||||
? [= Sender santad] claim
|
||||
? [= Sender santad] file /var/log/santa.log
|
||||
? [= Sender santactl] claim
|
||||
? [= Sender santactl] file /var/log/santa.log
|
||||
|
||||
@@ -6,19 +6,15 @@
|
||||
<string>com.google.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/libexec/santad</string>
|
||||
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>SantaXPCNotifications</key>
|
||||
<true/>
|
||||
<key>SantaXPCControl</key>
|
||||
<true/>
|
||||
<key>SantaXPCNotifications</key>
|
||||
<true/>
|
||||
<key>SantaXPCControl</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
|
||||
@@ -10,5 +10,7 @@
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!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.santasync</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/sbin/santactl</string>
|
||||
<string>sync</string>
|
||||
</array>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>ProcessType</key>
|
||||
<string>Background</string>
|
||||
<key>StartInterval</key>
|
||||
<integer>600</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -19,7 +19,6 @@ 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
|
||||
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
@@ -28,13 +27,18 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/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
|
||||
|
||||
# Copy new files.
|
||||
/bin/cp ${SOURCE}/binaries/santad /usr/libexec
|
||||
/bin/cp ${SOURCE}/binaries/santactl /usr/sbin
|
||||
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
|
||||
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
|
||||
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
|
||||
/bin/cp ${SOURCE}/conf/com.google.{santad,santasync}.plist /Library/LaunchDaemons
|
||||
/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/
|
||||
|
||||
@@ -46,7 +50,6 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
# Load santad and scheduled sync jobs.
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santasync.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
|
||||
5
Podfile
5
Podfile
@@ -5,12 +5,13 @@ inhibit_all_warnings!
|
||||
target :santad do
|
||||
pod 'FMDB'
|
||||
|
||||
post_install do |rep|
|
||||
rep.project.targets.each do |target|
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if config.name != 'Release' then
|
||||
break
|
||||
end
|
||||
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
|
||||
end
|
||||
|
||||
@@ -14,4 +14,4 @@ SPEC CHECKSUMS:
|
||||
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
|
||||
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
|
||||
|
||||
COCOAPODS: 0.36.3
|
||||
COCOAPODS: 0.38.0
|
||||
|
||||
17
Rakefile
17
Rakefile
@@ -4,7 +4,7 @@ WORKSPACE = 'Santa.xcworkspace'
|
||||
DEFAULT_SCHEME = 'All'
|
||||
OUTPUT_PATH = 'Build'
|
||||
DIST_PATH = 'Dist'
|
||||
BINARIES = ['Santa.app', 'santa-driver.kext', 'santad', 'santactl']
|
||||
BINARIES = ['Santa.app', 'santa-driver.kext']
|
||||
XCPRETTY_DEFAULTS = '-sc'
|
||||
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
|
||||
@@ -33,10 +33,8 @@ task :init do
|
||||
end
|
||||
|
||||
task :remove_existing do
|
||||
system 'sudo rm -rf /santa-driver.kext'
|
||||
system 'sudo rm -rf /Library/Extensions/santa-driver.kext'
|
||||
system 'sudo rm -rf /Applications/Santa.app'
|
||||
system 'sudo rm /usr/libexec/santad'
|
||||
system 'sudo rm /usr/sbin/santactl'
|
||||
end
|
||||
|
||||
desc "Clean"
|
||||
@@ -82,16 +80,13 @@ namespace :install do
|
||||
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.santasync.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 #{OUTPUT_PATH}/Products/#{config}/santa-driver.kext /"
|
||||
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/santa-driver.kext /Library/Extensions"
|
||||
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/Santa.app /Applications"
|
||||
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santad /usr/libexec"
|
||||
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santactl /usr/sbin"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -156,7 +151,7 @@ end
|
||||
|
||||
task :unload_gui do
|
||||
puts "Unloading GUI agent"
|
||||
system "sudo killall Santa 2>/dev/null"
|
||||
system "launchctl unload /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Unload"
|
||||
@@ -169,12 +164,12 @@ end
|
||||
|
||||
task :load_kext do
|
||||
puts "Loading kernel extension"
|
||||
system "sudo kextload /santa-driver.kext"
|
||||
system "sudo kextload /Library/Extensions/santa-driver.kext"
|
||||
end
|
||||
|
||||
task :load_gui do
|
||||
puts "Loading GUI agent"
|
||||
system "open /Applications/Santa.app"
|
||||
system "launchctl load /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Load"
|
||||
|
||||
@@ -118,6 +118,8 @@
|
||||
0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */; };
|
||||
0DC5D86E191AED220078A5C0 /* SNTRuleTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */; };
|
||||
0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */; };
|
||||
0DC765EA1B28D9EA00BAE651 /* santad in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0D9A7F3D1759330500035EB5 /* santad */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
0DC765EB1B28D9EA00BAE651 /* santactl in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0D35BD9E18FD71CE00921A21 /* santactl */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
0DCA552718C95928002A7DAE /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; };
|
||||
0DCD5FBF1909D64A006B445C /* SNTCommandBinaryInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandBinaryInfo.m */; };
|
||||
0DCD6042190ACCB8006B445C /* SNTFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD6041190ACCB8006B445C /* SNTFileInfo.m */; };
|
||||
@@ -203,8 +205,36 @@
|
||||
remoteGlobalIDString = 0D9A7F3C1759330400035EB5;
|
||||
remoteInfo = santad;
|
||||
};
|
||||
0DC765E51B28D9C600BAE651 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 0D9A7F3C1759330400035EB5;
|
||||
remoteInfo = santad;
|
||||
};
|
||||
0DC765E71B28D9C600BAE651 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 0D35BD9D18FD71CE00921A21;
|
||||
remoteInfo = santactl;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
0DC765E91B28D9CB00BAE651 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 6;
|
||||
files = (
|
||||
0DC765EA1B28D9EA00BAE651 /* santad in CopyFiles */,
|
||||
0DC765EB1B28D9EA00BAE651 /* santactl in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0A84545E322F475FA0B505D5 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0D0016A2192BCD3C005E7FCD /* KernelTests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = KernelTests; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -835,10 +865,13 @@
|
||||
0DD98E691A5DD5C900A754C6 /* Update Module Version */,
|
||||
0D91BCAE174E8A7E00131A7D /* Sources */,
|
||||
0D91BCB0174E8A7E00131A7D /* Headers */,
|
||||
0DC765E91B28D9CB00BAE651 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
0DC765E61B28D9C600BAE651 /* PBXTargetDependency */,
|
||||
0DC765E81B28D9C600BAE651 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "santa-driver";
|
||||
productName = "santa-driver";
|
||||
@@ -1261,6 +1294,16 @@
|
||||
target = 0D9A7F3C1759330400035EB5 /* santad */;
|
||||
targetProxy = 0D9A7F591759393600035EB5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
0DC765E61B28D9C600BAE651 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 0D9A7F3C1759330400035EB5 /* santad */;
|
||||
targetProxy = 0DC765E51B28D9C600BAE651 /* PBXContainerItemProxy */;
|
||||
};
|
||||
0DC765E81B28D9C600BAE651 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 0D35BD9D18FD71CE00921A21 /* santactl */;
|
||||
targetProxy = 0DC765E71B28D9C600BAE651 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -1293,6 +1336,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INSTALL_PATH = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
WARNING_CFLAGS = "";
|
||||
@@ -1322,6 +1366,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INSTALL_PATH = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
WARNING_CFLAGS = "";
|
||||
@@ -1456,6 +1501,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "${DERIVED_FILE_DIR}/santactl-Info.plist";
|
||||
INSTALL_PATH = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
};
|
||||
@@ -1489,6 +1535,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "${DERIVED_FILE_DIR}/santactl-Info.plist";
|
||||
INSTALL_PATH = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
};
|
||||
@@ -1622,6 +1669,7 @@
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
@@ -1660,6 +1708,7 @@
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "";
|
||||
@@ -1725,7 +1774,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "${DERIVED_FILE_DIR}/santad-Info.plist";
|
||||
INSTALL_PATH = /usr/sbin;
|
||||
INSTALL_PATH = "";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
@@ -1753,7 +1802,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "${DERIVED_FILE_DIR}/santad-Info.plist";
|
||||
INSTALL_PATH = /usr/sbin;
|
||||
INSTALL_PATH = "";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14C1514" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
|
||||
@@ -14,7 +14,7 @@
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES"/>
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
@@ -65,6 +65,9 @@ There are no user-configurable settings.</string>
|
||||
<buttonCell key="cell" type="push" title="Dismiss" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uSw-o1-lWW">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="orderOut:" target="-1" id="6oW-zI-zn5"/>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<window 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="497" height="356"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1600"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="497" height="356"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -155,7 +155,7 @@
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
<color key="borderColor" white="0.0" alpha="0.18" colorSpace="calibratedWhite"/>
|
||||
<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>
|
||||
@@ -234,6 +234,7 @@ DQ
|
||||
</subviews>
|
||||
<constraints>
|
||||
<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="eQb-0a-76J" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="-116" id="6Q5-Oo-1cI"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
|
||||
@@ -255,7 +256,6 @@ DQ
|
||||
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="top" secondItem="eQb-0a-76J" secondAttribute="bottom" constant="8" id="abm-cM-PN0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="BbV-3h-mmL" secondAttribute="centerX" priority="900" constant="-62.5" id="acs-5J-vQY"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="eQb-0a-76J" secondAttribute="trailing" constant="20" id="b0B-3w-grH"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="centerY" secondItem="PXc-xv-A28" secondAttribute="centerY" id="cHe-pZ-0Oq"/>
|
||||
|
||||
@@ -70,8 +70,7 @@
|
||||
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
|
||||
self.listener.exportedObject = self.notificationManager;
|
||||
self.listener.rejectedHandler = ^{
|
||||
[weakSelf performSelectorInBackground:@selector(attemptReconnection)
|
||||
withObject:nil];
|
||||
[weakSelf attemptReconnection];
|
||||
};
|
||||
self.listener.invalidationHandler = self.listener.rejectedHandler;
|
||||
[self.listener resume];
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
///
|
||||
/// The custom message to display for this event
|
||||
///
|
||||
@property NSString *customMessage;
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
///
|
||||
/// The delegate to inform when the notification is dismissed
|
||||
|
||||
@@ -65,4 +65,8 @@ typedef enum {
|
||||
EVENTSTATE_MAX
|
||||
} santa_eventstate_t;
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santad";
|
||||
static const char *kSantaCtlPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl";
|
||||
|
||||
#endif // SANTA__COMMON__COMMONENUMS_H
|
||||
|
||||
@@ -35,6 +35,15 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(nonatomic) BOOL logAllEvents;
|
||||
|
||||
///
|
||||
/// 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 a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
///
|
||||
@@ -71,6 +80,12 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@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.
|
||||
///
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
@interface SNTConfigurator ()
|
||||
@property NSString *configFilePath;
|
||||
@property NSMutableDictionary *configData;
|
||||
|
||||
/// Creating NSRegularExpression objects is not fast, so cache it.
|
||||
@property NSRegularExpression *cachedWhitelistDirRegex;
|
||||
|
||||
/// 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
|
||||
@@ -29,7 +35,7 @@ NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
|
||||
/// The keys in the config file
|
||||
static NSString * const kClientModeKey = @"ClientMode";
|
||||
|
||||
static NSString * const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString * const kLogAllEventsKey = @"LogAllEvents";
|
||||
|
||||
static NSString * const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
@@ -73,6 +79,12 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return sharedConfigurator;
|
||||
}
|
||||
|
||||
#pragma mark Protected Keys
|
||||
|
||||
- (NSArray *)protectedKeys {
|
||||
return @[ kClientModeKey, kWhitelistRegexKey ];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (santa_clientmode_t)clientMode {
|
||||
@@ -92,6 +104,27 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)whitelistPathRegex {
|
||||
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:nil];
|
||||
}
|
||||
return self.cachedWhitelistDirRegex;
|
||||
}
|
||||
|
||||
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kWhitelistRegexKey];
|
||||
} else {
|
||||
self.configData[kWhitelistRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedWhitelistDirRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (BOOL)logAllEvents {
|
||||
return [self.configData[kLogAllEventsKey] boolValue];
|
||||
}
|
||||
@@ -204,17 +237,20 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure no-one is trying to change the client mode behind Santa's back.
|
||||
if (self.configData[kClientModeKey] && configData[kClientModeKey] &&
|
||||
![self.configData[kClientModeKey] isEqual:configData[kClientModeKey]] &&
|
||||
geteuid() == 0) {
|
||||
NSMutableDictionary *configDataMutable = [configData mutableCopy];
|
||||
configDataMutable[kClientModeKey] = self.configData[kClientModeKey];
|
||||
self.configData = configDataMutable;
|
||||
[self saveConfigToDisk];
|
||||
} else {
|
||||
self.configData = [configData mutableCopy];
|
||||
// Ensure no-one is trying to change protected keys behind our back.
|
||||
NSMutableDictionary *configDataMutable = [configData mutableCopy];
|
||||
BOOL changed = NO;
|
||||
for (NSString *key in self.protectedKeys) {
|
||||
if (self.configData[key] && configData[key] &&
|
||||
![self.configData[key] isEqual:configData[key]] && geteuid() == 0) {
|
||||
NSMutableDictionary *configDataMutable = [configData mutableCopy];
|
||||
configDataMutable[key] = self.configData[key];
|
||||
changed = YES;
|
||||
LOGD(@"Ignoring changed configuration key: %@", key);
|
||||
}
|
||||
}
|
||||
self.configData = configDataMutable;
|
||||
if (changed) [self saveConfigToDisk];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
@@ -23,6 +23,16 @@
|
||||
///
|
||||
/// @param path The path of the file this instance is to represent. The path will be
|
||||
/// converted to an absolute, standardized path if it isn't already.
|
||||
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
|
||||
/// describing the problem.
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// Convenience initializer.
|
||||
///
|
||||
/// @param path The path to the file this instance is to represent. The path will be
|
||||
/// converted to an absolute, standardized path if it isn't already.
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
|
||||
@@ -23,52 +23,47 @@
|
||||
@interface SNTFileInfo ()
|
||||
@property NSString *path;
|
||||
@property NSData *fileData;
|
||||
|
||||
// Cached properties
|
||||
@property NSData *firstMachHeaderData;
|
||||
@property NSBundle *bundleRef;
|
||||
@property NSDictionary *infoDict;
|
||||
@property NSArray *architecturesArray;
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfo
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
// Convert to absolute, standardized path
|
||||
path = [path stringByResolvingSymlinksInPath];
|
||||
if (![path isAbsolutePath]) {
|
||||
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
|
||||
path = [cwd stringByAppendingPathComponent:path];
|
||||
}
|
||||
path = [path stringByStandardizingPath];
|
||||
|
||||
// Determine if file exists.
|
||||
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
|
||||
BOOL directory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
|
||||
return nil;
|
||||
} else if (directory) {
|
||||
NSString *infoPath = [path stringByAppendingPathComponent:@"Contents/Info.plist"];
|
||||
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:infoPath];
|
||||
if (d && d[@"CFBundleExecutable"]) {
|
||||
path = [path stringByAppendingPathComponent:@"Contents/MacOS"];
|
||||
_path = [path stringByAppendingPathComponent:d[@"CFBundleExecutable"]];
|
||||
} else {
|
||||
return nil;
|
||||
_path = [self resolvePath:path];
|
||||
if (_path.length == 0) {
|
||||
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 }];
|
||||
}
|
||||
} else {
|
||||
_path = path;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_fileData = [NSData dataWithContentsOfFile:_path options:NSDataReadingMappedIfSafe error:nil];
|
||||
if (!_fileData) return nil;
|
||||
_fileData = [NSData dataWithContentsOfFile:_path
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:error];
|
||||
if (_fileData.length == 0) return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
return [self initWithPath:path error:NULL];
|
||||
}
|
||||
|
||||
- (NSString *)SHA1 {
|
||||
unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1([self.fileData bytes], (unsigned int)[self.fileData length], sha1);
|
||||
CC_SHA1(self.fileData.bytes, (unsigned int)self.fileData.length, sha1);
|
||||
|
||||
// Convert the binary SHA into hex
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
@@ -80,12 +75,12 @@
|
||||
}
|
||||
|
||||
- (NSString *)SHA256 {
|
||||
unsigned char sha2[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(self.fileData.bytes, (unsigned int)self.fileData.length, sha2);
|
||||
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(self.fileData.bytes, (unsigned int)self.fileData.length, sha256);
|
||||
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)sha2[i]];
|
||||
[buf appendFormat:@"%02x", (unsigned char)sha256[i]];
|
||||
}
|
||||
|
||||
return buf;
|
||||
@@ -101,42 +96,45 @@
|
||||
}
|
||||
|
||||
- (NSArray *)architectures {
|
||||
if (![self isMachO]) return nil;
|
||||
if (!self.architecturesArray) {
|
||||
self.architecturesArray = (NSArray *)[NSNull null];
|
||||
|
||||
if ([self isFat]) {
|
||||
NSMutableArray *ret = [[NSMutableArray alloc] init];
|
||||
if ([self isFat]) {
|
||||
NSMutableArray *ret = [[NSMutableArray alloc] init];
|
||||
|
||||
// Retrieve just the fat_header, if possible.
|
||||
NSData *head = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct fat_header))];
|
||||
if (!head) return nil;
|
||||
struct fat_header *fat_header = (struct fat_header *)[head bytes];
|
||||
// Retrieve just the fat_header, if possible.
|
||||
NSData *head = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct fat_header))];
|
||||
if (!head) return nil;
|
||||
struct fat_header *fat_header = (struct fat_header *)[head bytes];
|
||||
|
||||
// Get number of architectures in the binary
|
||||
uint32_t narch = NSSwapBigIntToHost(fat_header->nfat_arch);
|
||||
// Get number of architectures in the binary
|
||||
uint32_t narch = NSSwapBigIntToHost(fat_header->nfat_arch);
|
||||
|
||||
// Retrieve just the fat_arch's, make a mutable copy and if necessary swap the bytes
|
||||
NSData *archs = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
|
||||
sizeof(struct fat_arch) * narch)];
|
||||
if (!archs) return nil;
|
||||
struct fat_arch *fat_archs = (struct fat_arch *)[archs bytes];
|
||||
// Retrieve just the fat_arch's, make a mutable copy and if necessary swap the bytes
|
||||
NSData *archs = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
|
||||
sizeof(struct fat_arch) * narch)];
|
||||
if (!archs) return nil;
|
||||
struct fat_arch *fat_archs = (struct fat_arch *)[archs bytes];
|
||||
|
||||
// For each arch, get the name of it's architecture
|
||||
for (uint32_t i = 0; i < narch; ++i) {
|
||||
cpu_type_t cpu = (cpu_type_t)NSSwapBigIntToHost((unsigned int)fat_archs[i].cputype);
|
||||
[ret addObject:[self nameForCPUType:cpu]];
|
||||
// For each arch, get the name of its architecture
|
||||
for (uint32_t i = 0; i < narch; ++i) {
|
||||
cpu_type_t cpu = (cpu_type_t)NSSwapBigIntToHost((unsigned int)fat_archs[i].cputype);
|
||||
[ret addObject:[self nameForCPUType:cpu]];
|
||||
}
|
||||
|
||||
self.architecturesArray = ret;
|
||||
} else if ([self firstMachHeader]) {
|
||||
struct mach_header *hdr = [self firstMachHeader];
|
||||
self.architecturesArray = @[ [self nameForCPUType:hdr->cputype] ];
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
struct mach_header *hdr = [self firstMachHeader];
|
||||
return @[ [self nameForCPUType:hdr->cputype] ];
|
||||
}
|
||||
return nil;
|
||||
|
||||
return self.architecturesArray == (NSArray *)[NSNull null] ? nil : self.architecturesArray;
|
||||
}
|
||||
|
||||
- (BOOL)isDylib {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_DYLIB || mach_header->filetype == MH_FVMLIB) {
|
||||
if (mach_header && (mach_header->filetype == MH_DYLIB || mach_header->filetype == MH_FVMLIB)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -145,8 +143,7 @@
|
||||
|
||||
- (BOOL)isKext {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_KEXT_BUNDLE) {
|
||||
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -154,8 +151,7 @@
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
return ([self.fileData length] >= 160 &&
|
||||
([self isMachHeader:(struct mach_header *)[self.fileData bytes]] || [self isFat]));
|
||||
return [self firstMachHeader] != nil;
|
||||
}
|
||||
|
||||
- (BOOL)isFat {
|
||||
@@ -163,8 +159,6 @@
|
||||
}
|
||||
|
||||
- (BOOL)isScript {
|
||||
if ([self.fileData length] < 1) return NO;
|
||||
|
||||
char magic[2];
|
||||
[self.fileData getBytes:&magic length:2];
|
||||
|
||||
@@ -206,21 +200,19 @@
|
||||
/// NSBundle reference for Bundle.app.
|
||||
///
|
||||
- (NSBundle *)bundle {
|
||||
if (self.bundleRef) return self.bundleRef;
|
||||
if (!self.bundleRef) {
|
||||
self.bundleRef = (NSBundle *)[NSNull null];
|
||||
|
||||
NSArray *pathComponents = [self.path pathComponents];
|
||||
// Check that the full path is at least 4-levels deep:
|
||||
// e.g: /Calendar.app/Contents/MacOS/Calendar
|
||||
NSArray *pathComponents = [self.path pathComponents];
|
||||
if ([pathComponents count] < 4) return nil;
|
||||
|
||||
// Check that the full path is at least 4-levels deep:
|
||||
// e.g: /Calendar.app/Contents/MacOS/Calendar
|
||||
if ([pathComponents count] < 4) return nil;
|
||||
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
|
||||
self.bundleRef = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
|
||||
// Clear the bundle if it doesn't have a bundle ID
|
||||
if (![self.bundleRef objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = nil;
|
||||
|
||||
return self.bundleRef;
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 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;
|
||||
}
|
||||
|
||||
- (NSString *)bundlePath {
|
||||
@@ -228,33 +220,37 @@
|
||||
}
|
||||
|
||||
- (NSDictionary *)infoPlist {
|
||||
if (self.infoDict) return self.infoDict;
|
||||
if (!self.infoDict) {
|
||||
self.infoDict = (NSDictionary *)[NSNull null];
|
||||
|
||||
if ([self bundle]) {
|
||||
self.infoDict = [[self bundle] infoDictionary];
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
|
||||
self.infoDict =
|
||||
if ([self bundle] && [self.bundle infoDictionary]) {
|
||||
self.infoDict = [self.bundle infoDictionary];
|
||||
} else {
|
||||
// Binaries with embedded Info.plist aren't in an NSBundle but
|
||||
// CFBundleCopyInfoDictionaryForURL will return the embedded info dict.
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
|
||||
NSDictionary *infoDict =
|
||||
(__bridge_transfer NSDictionary *)CFBundleCopyInfoDictionaryForURL((__bridge CFURLRef)url);
|
||||
return self.infoDict;
|
||||
if (infoDict) self.infoDict = infoDict;
|
||||
}
|
||||
}
|
||||
return self.infoDict == (NSDictionary *)[NSNull null] ? nil : self.infoDict;
|
||||
}
|
||||
|
||||
- (NSString *)bundleIdentifier {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleIdentifier"];
|
||||
return [self.infoPlist objectForKey:@"CFBundleIdentifier"];
|
||||
}
|
||||
|
||||
- (NSString *)bundleName {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleName"];
|
||||
return [self.infoPlist objectForKey:@"CFBundleName"];
|
||||
}
|
||||
|
||||
- (NSString *)bundleVersion {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleVersion"];
|
||||
return [self.infoPlist objectForKey:@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
- (NSString *)bundleShortVersionString {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleShortVersionString"];
|
||||
return [self.infoPlist objectForKey:@"CFBundleShortVersionString"];
|
||||
}
|
||||
|
||||
- (NSArray *)downloadURLs {
|
||||
@@ -290,30 +286,31 @@
|
||||
/// architecture-specific header.
|
||||
///
|
||||
- (struct mach_header *)firstMachHeader {
|
||||
if (![self isMachO]) return NULL;
|
||||
if (!self.firstMachHeaderData) {
|
||||
self.firstMachHeaderData = (NSData *)[NSNull null];
|
||||
|
||||
struct mach_header *mach_header = (struct mach_header *)[self.fileData bytes];
|
||||
struct fat_header *fat_header = (struct fat_header *)[self.fileData bytes];
|
||||
if ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]) {
|
||||
// Get the bytes for the fat_arch
|
||||
NSData *archHdr = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
|
||||
sizeof(struct fat_arch))];
|
||||
if (!archHdr) return NULL;
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[archHdr bytes];
|
||||
|
||||
if ([self isFatHeader:fat_header]) {
|
||||
// Get the bytes for the fat_arch
|
||||
NSData *archHdr =
|
||||
[self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch))];
|
||||
if (!archHdr) return nil;
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[archHdr bytes];
|
||||
// Get bytes for first mach_header
|
||||
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(NSSwapBigIntToHost(fat_arch->offset),
|
||||
sizeof(struct mach_header))];
|
||||
if (!machHdr || ![self isMachHeader:(struct mach_header *)machHdr.bytes]) return NULL;
|
||||
|
||||
// Get bytes for first mach_header
|
||||
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(NSSwapBigIntToHost(fat_arch->offset),
|
||||
sizeof(struct mach_header))];
|
||||
if (!machHdr) return nil;
|
||||
mach_header = (struct mach_header *)[machHdr bytes];
|
||||
self.firstMachHeaderData = [machHdr copy];
|
||||
} else if ([self isMachHeader:(struct mach_header *)[self.fileData bytes]]) {
|
||||
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct mach_header))];
|
||||
if (!machHdr) return NULL;
|
||||
self.firstMachHeaderData = [machHdr copy];
|
||||
}
|
||||
}
|
||||
|
||||
if ([self isMachHeader:mach_header]) {
|
||||
return mach_header;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return (self.firstMachHeaderData == (NSData *)[NSNull null] ?
|
||||
NULL :
|
||||
(struct mach_header *)self.firstMachHeaderData.bytes);
|
||||
}
|
||||
|
||||
- (BOOL)isMachHeader:(struct mach_header *)header {
|
||||
@@ -354,4 +351,32 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)resolvePath:(NSString *)path {
|
||||
// Convert to absolute, standardized path
|
||||
path = [path stringByResolvingSymlinksInPath];
|
||||
if (![path isAbsolutePath]) {
|
||||
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
|
||||
path = [cwd stringByAppendingPathComponent:path];
|
||||
}
|
||||
path = [path stringByStandardizingPath];
|
||||
|
||||
// Determine if file exists.
|
||||
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
|
||||
BOOL directory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
|
||||
return nil;
|
||||
} else if (directory) {
|
||||
NSString *infoPath = [path stringByAppendingPathComponent:@"Contents/Info.plist"];
|
||||
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:infoPath];
|
||||
if (d && d[@"CFBundleExecutable"]) {
|
||||
path = [path stringByAppendingPathComponent:@"Contents/MacOS"];
|
||||
return [path stringByAppendingPathComponent:d[@"CFBundleExecutable"]];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
close(fd);
|
||||
}
|
||||
|
||||
while ((fd = open([weakSelf.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
|
||||
const char *filePathCString = [weakSelf.filePath fileSystemRepresentation];
|
||||
while ((fd = open(filePathCString, O_EVTONLY)) < 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,19 +39,20 @@
|
||||
|
||||
///
|
||||
/// Logging function.
|
||||
///
|
||||
/// @param level one of the levels defined above
|
||||
/// @param destination a FILE, generally should be stdout or stderr
|
||||
/// @param destination a FILE, generally stdout/stderr. If the file is closed, the log
|
||||
/// will instead be sent to syslog.
|
||||
/// @param format the printf style format string
|
||||
/// @param ... the arguments to format.
|
||||
///
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...);
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...)
|
||||
__attribute__((format(__NSString__, 3, 4)));
|
||||
|
||||
/// Simple logging macros
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__);
|
||||
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__);
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__);
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__);
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
|
||||
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#import "SNTLogging.h"
|
||||
|
||||
#import <sys/syslog.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static int logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
@@ -21,15 +23,10 @@ static int logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
static NSDateFormatter *dateFormatter;
|
||||
static NSString *binaryName;
|
||||
static dispatch_once_t pred;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
||||
[dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS'Z"];
|
||||
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
@@ -50,14 +47,20 @@ void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
fprintf(destination, "%s\n", [s UTF8String]);
|
||||
} else {
|
||||
NSString *levelName;
|
||||
int syslogLevel = LOG_DEBUG;
|
||||
switch (level) {
|
||||
case LOG_LEVEL_ERROR: levelName = @"E"; break;
|
||||
case LOG_LEVEL_WARN: levelName = @"W"; break;
|
||||
case LOG_LEVEL_INFO: levelName = @"I"; break;
|
||||
case LOG_LEVEL_DEBUG: levelName = @"D"; break;
|
||||
case LOG_LEVEL_ERROR: levelName = @"E"; syslogLevel = LOG_ERR; break;
|
||||
case LOG_LEVEL_WARN: levelName = @"W"; syslogLevel = LOG_WARNING; break;
|
||||
case LOG_LEVEL_INFO: levelName = @"I"; syslogLevel = LOG_INFO; break;
|
||||
case LOG_LEVEL_DEBUG: levelName = @"D"; syslogLevel = LOG_DEBUG; break;
|
||||
}
|
||||
|
||||
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
|
||||
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
|
||||
if (fileno(destination) == -1) {
|
||||
syslog(syslogLevel, "%s\n",
|
||||
[[NSString stringWithFormat:@"%@ %@: %@", levelName, binaryName, s] UTF8String]);
|
||||
} else {
|
||||
fprintf(destination, "%s\n",
|
||||
[[NSString stringWithFormat:@"%@ %@: %@", levelName, binaryName, s] UTF8String]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
///
|
||||
/// The hash of the object this rule is for
|
||||
///
|
||||
@property NSString *shasum;
|
||||
@property(copy) NSString *shasum;
|
||||
|
||||
///
|
||||
/// The state of this rule
|
||||
@@ -37,7 +37,7 @@
|
||||
///
|
||||
/// A custom message that will be displayed if this rule blocks a binary from executing
|
||||
///
|
||||
@property NSString *customMsg;
|
||||
@property(copy) NSString *customMsg;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
|
||||
@@ -41,10 +41,12 @@
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
|
||||
///
|
||||
/// Misc ops
|
||||
/// Config ops
|
||||
///
|
||||
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
|
||||
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
|
||||
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -23,45 +23,35 @@ bool SantaDecisionManager::init() {
|
||||
if (!super::init()) return false;
|
||||
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", lck_grp_attr_alloc_init());
|
||||
dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, lck_attr_alloc_init());
|
||||
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_,
|
||||
lck_attr_alloc_init());
|
||||
|
||||
cached_decisions_ = OSDictionary::withCapacity(1000);
|
||||
|
||||
dataqueue_ = IOSharedDataQueue::withCapacity((sizeof(santa_message_t) +
|
||||
DATA_QUEUE_ENTRY_HEADER_SIZE)
|
||||
* kMaxQueueEvents);
|
||||
dataqueue_ = IOSharedDataQueue::withEntries(kMaxQueueEvents,
|
||||
sizeof(santa_message_t));
|
||||
if (!dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
shared_memory_ = dataqueue_->getMemoryDescriptor();
|
||||
if (!shared_memory_) return kIOReturnNoMemory;
|
||||
|
||||
client_pid_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::free() {
|
||||
if (shared_memory_) {
|
||||
shared_memory_->release();
|
||||
shared_memory_ = NULL;
|
||||
}
|
||||
|
||||
if (dataqueue_) {
|
||||
dataqueue_->release();
|
||||
dataqueue_ = NULL;
|
||||
}
|
||||
|
||||
if (cached_decisions_) {
|
||||
cached_decisions_->release();
|
||||
cached_decisions_ = NULL;
|
||||
}
|
||||
OSSafeReleaseNULL(dataqueue_);
|
||||
OSSafeReleaseNULL(cached_decisions_);
|
||||
|
||||
if (cached_decisions_lock_) {
|
||||
lck_rw_free(cached_decisions_lock_, sdm_lock_grp_);
|
||||
cached_decisions_lock_ = NULL;
|
||||
}
|
||||
|
||||
if (dataqueue_lock_) {
|
||||
lck_mtx_free(dataqueue_lock_, sdm_lock_grp_);
|
||||
dataqueue_lock_ = NULL;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_) {
|
||||
lck_grp_free(sdm_lock_grp_);
|
||||
sdm_lock_grp_ = NULL;
|
||||
@@ -79,10 +69,12 @@ void SantaDecisionManager::ConnectClient(mach_port_t port, pid_t pid) {
|
||||
// connected should be cleared
|
||||
ClearCache();
|
||||
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(dataqueue_lock_);
|
||||
|
||||
client_pid_ = pid;
|
||||
client_proc_ = proc_find(pid);
|
||||
|
||||
failed_queue_requests_ = 0;
|
||||
}
|
||||
|
||||
@@ -95,12 +87,17 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
|
||||
if (!itDied) {
|
||||
santa_message_t message = {.action = ACTION_REQUEST_SHUTDOWN};
|
||||
PostToQueue(message);
|
||||
dataqueue_->setNotificationPort(NULL);
|
||||
} else {
|
||||
// If the client died, reset the data queue so when it reconnects
|
||||
// it doesn't get swamped straight away.
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
dataqueue_->release();
|
||||
dataqueue_ = IOSharedDataQueue::withEntries(kMaxQueueEvents,
|
||||
sizeof(santa_message_t));
|
||||
lck_mtx_unlock(dataqueue_lock_);
|
||||
}
|
||||
|
||||
dataqueue_->setNotificationPort(NULL);
|
||||
|
||||
proc_rele(client_proc_);
|
||||
client_proc_ = NULL;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::ClientConnected() {
|
||||
@@ -108,7 +105,7 @@ bool SantaDecisionManager::ClientConnected() {
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetMemoryDescriptor() {
|
||||
return shared_memory_;
|
||||
return dataqueue_->getMemoryDescriptor();
|
||||
}
|
||||
|
||||
#pragma mark Listener Control
|
||||
@@ -185,7 +182,12 @@ void SantaDecisionManager::CacheCheck(const char *identifier) {
|
||||
lck_rw_lock_shared(cached_decisions_lock_);
|
||||
bool shouldInvalidate = (cached_decisions_->getObject(identifier) != NULL);
|
||||
if (shouldInvalidate) {
|
||||
lck_rw_lock_shared_to_exclusive(cached_decisions_lock_);
|
||||
if (!lck_rw_lock_shared_to_exclusive(cached_decisions_lock_)) {
|
||||
// shared_to_exclusive will return false if a previous reader upgraded
|
||||
// and if that happens the lock will have been unlocked. If that happens,
|
||||
// which is rare, relock exclusively.
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
}
|
||||
cached_decisions_->removeObject(identifier);
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
} else {
|
||||
@@ -247,7 +249,7 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
santa_message_t message, char *vnode_id_str) {
|
||||
const santa_message_t message, const char *vnode_id_str) {
|
||||
santa_action_t return_action = ACTION_UNSET;
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
@@ -265,15 +267,11 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
// ... and wait for it to respond. If after kRequestLoopSleepMilliseconds
|
||||
// * kMaxRequestLoops it still hasn't responded, send request again.
|
||||
for (int i = 0; i < kMaxRequestLoops; ++i) {
|
||||
do {
|
||||
IOSleep(kRequestLoopSleepMilliseconds);
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
if (CHECKBW_RESPONSE_VALID(return_action)) break;
|
||||
}
|
||||
} while (!CHECKBW_RESPONSE_VALID(return_action) &&
|
||||
proc_exiting(client_proc_) == 0);
|
||||
} while (return_action == ACTION_REQUEST_CHECKBW && ClientConnected());
|
||||
} while (!CHECKBW_RESPONSE_VALID(return_action) && ClientConnected());
|
||||
|
||||
// If response is still not valid, the daemon exited
|
||||
if (!CHECKBW_RESPONSE_VALID(return_action)) {
|
||||
@@ -282,21 +280,17 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
CacheCheck(vnode_id_str);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t credential,
|
||||
const vfs_context_t vfs_context,
|
||||
const vnode_t vnode) {
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str) {
|
||||
santa_action_t return_action = ACTION_UNSET;
|
||||
|
||||
// Fetch Vnode ID & string
|
||||
uint64_t vnode_id = GetVnodeIDForVnode(vfs_context, vnode);
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
// Check to see if item is in cache
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
|
||||
@@ -309,14 +303,14 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
int name_len = MAXPATHLEN;
|
||||
if (vn_getpath(vnode, path, &name_len) != 0) {
|
||||
if (vn_getpath(vp, path, &name_len) != 0) {
|
||||
path[0] = '\0';
|
||||
}
|
||||
|
||||
// Prepare message to send to daemon.
|
||||
santa_message_t message = {};
|
||||
strlcpy(message.path, path, sizeof(message.path));
|
||||
message.userId = kauth_cred_getuid(credential);
|
||||
message.userId = kauth_cred_getuid(cred);
|
||||
message.pid = proc_selfpid();
|
||||
message.ppid = proc_selfppid();
|
||||
message.action = ACTION_REQUEST_CHECKBW;
|
||||
@@ -336,16 +330,18 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
|
||||
bool SantaDecisionManager::PostToQueue(santa_message_t message) {
|
||||
bool kr = false;
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
kr = dataqueue_->enqueue(&message, sizeof(message));
|
||||
lck_mtx_unlock(dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::GetVnodeIDForVnode(
|
||||
const vfs_context_t context, const vnode_t vp) {
|
||||
const vfs_context_t ctx, const vnode_t vp) {
|
||||
struct vnode_attr vap;
|
||||
VATTR_INIT(&vap);
|
||||
VATTR_WANTED(&vap, va_fileid);
|
||||
vnode_getattr(vp, &vap, context);
|
||||
vnode_getattr(vp, &vap, ctx);
|
||||
return vap.va_fileid;
|
||||
}
|
||||
|
||||
@@ -366,6 +362,54 @@ void SantaDecisionManager::DecrementListenerInvocations() {
|
||||
OSDecrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vfs_context_t ctx,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Only operate on regular files (not directories, symlinks, etc.).
|
||||
vtype vt = vnode_vtype(vp);
|
||||
if (vt != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Get ID for the vnode and convert it to a string.
|
||||
uint64_t vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
char vnode_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
// Fetch decision
|
||||
santa_action_t returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
|
||||
|
||||
// If file has dirty blocks, remove from cache and deny. This would usually
|
||||
// be the case if a file has been written to and flushed but not yet
|
||||
// closed.
|
||||
if (vnode_hasdirtyblks(vp)) {
|
||||
CacheCheck(vnode_str);
|
||||
returnedAction = ACTION_RESPOND_CHECKBW_DENY;
|
||||
}
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_CHECKBW_ALLOW:
|
||||
return KAUTH_RESULT_ALLOW;
|
||||
case ACTION_RESPOND_CHECKBW_DENY:
|
||||
*errno = EPERM;
|
||||
return KAUTH_RESULT_DENY;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
// we don't break user's machines.
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
int SantaDecisionManager::FileOpCallback(const vnode_t vp) {
|
||||
vfs_context_t context = vfs_context_create(NULL);
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
|
||||
GetVnodeIDForVnode(context, vp));
|
||||
CacheCheck(vnode_id_str);
|
||||
vfs_context_rele(context);
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
#pragma mark Kauth Callbacks
|
||||
@@ -373,26 +417,17 @@ void SantaDecisionManager::DecrementListenerInvocations() {
|
||||
extern "C" int fileop_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
if (!(action == KAUTH_FILEOP_CLOSE && arg2 & KAUTH_FILEOP_CLOSE_MODIFIED)) {
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
if (idata == NULL) {
|
||||
LOGE("FileOp callback established without valid decision manager.");
|
||||
if (action != KAUTH_FILEOP_CLOSE ||
|
||||
!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED) ||
|
||||
idata == NULL) {
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
SantaDecisionManager *sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
sdm->IncrementListenerInvocations();
|
||||
|
||||
vfs_context_t context = vfs_context_create(NULL);
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
|
||||
sdm->GetVnodeIDForVnode(context, (vnode_t)arg0));
|
||||
sdm->CacheCheck(vnode_id_str);
|
||||
vfs_context_rele(context);
|
||||
|
||||
sdm->FileOpCallback(reinterpret_cast<vnode_t>(arg0));
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
@@ -401,64 +436,20 @@ extern "C" int fileop_scope_callback(
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
// The default action is to defer
|
||||
int returnResult = KAUTH_RESULT_DEFER;
|
||||
|
||||
// Cast arguments to correct types
|
||||
if (idata == NULL) {
|
||||
LOGE("Vnode callback established without valid decision manager.");
|
||||
return returnResult;
|
||||
if (action & KAUTH_VNODE_ACCESS ||
|
||||
!(action & KAUTH_VNODE_EXECUTE) ||
|
||||
idata == NULL) {
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
SantaDecisionManager *sdm =
|
||||
OSDynamicCast(SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
vfs_context_t vfs_context = reinterpret_cast<vfs_context_t>(arg0);
|
||||
vnode_t vnode = reinterpret_cast<vnode_t>(arg1);
|
||||
|
||||
// Only operate on regular files (not directories, symlinks, etc.)
|
||||
vtype vt = vnode_vtype(vnode);
|
||||
if (vt != VREG) return returnResult;
|
||||
|
||||
// Don't operate on ACCESS events, as they're advisory
|
||||
if (action & KAUTH_VNODE_ACCESS) return returnResult;
|
||||
|
||||
// Filter for only EXECUTE actions
|
||||
if (action & KAUTH_VNODE_EXECUTE) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
|
||||
// Fetch decision
|
||||
santa_action_t returnedAction =
|
||||
sdm->FetchDecision(credential, vfs_context, vnode);
|
||||
|
||||
// If file has dirty blocks, remove from cache and deny. This would usually
|
||||
// be the case if a file has been written to and flushed but not yet
|
||||
// closed.
|
||||
if (vnode_hasdirtyblks(vnode)) {
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
|
||||
sdm->GetVnodeIDForVnode(vfs_context, vnode));
|
||||
sdm->CacheCheck(vnode_id_str);
|
||||
returnedAction = ACTION_RESPOND_CHECKBW_DENY;
|
||||
}
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_CHECKBW_ALLOW:
|
||||
returnResult = KAUTH_RESULT_ALLOW;
|
||||
break;
|
||||
case ACTION_RESPOND_CHECKBW_DENY:
|
||||
*(reinterpret_cast<int *>(arg3)) = EACCES;
|
||||
returnResult = KAUTH_RESULT_DENY;
|
||||
break;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
// we don't break user's machines.
|
||||
break;
|
||||
}
|
||||
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return returnResult;
|
||||
}
|
||||
|
||||
return returnResult;
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
reinterpret_cast<vnode_t>(arg1),
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -82,36 +82,29 @@ class SantaDecisionManager : public OSObject {
|
||||
/// Clears the cache.
|
||||
void ClearCache();
|
||||
|
||||
/// Fetches a response from the cache, first checking to see if the
|
||||
/// entry has expired.
|
||||
santa_action_t GetFromCache(const char *identifier);
|
||||
|
||||
/// Fetches a response from the daemon.
|
||||
santa_action_t GetFromDaemon(santa_message_t message, char *identifier);
|
||||
|
||||
/// Fetches an execution decision for a file, first using the cache and then
|
||||
/// by sending a message to the daemon and waiting until a response arrives.
|
||||
/// If a daemon isn't connected, will allow execution and cache, logging
|
||||
/// the path to the executed file.
|
||||
santa_action_t FetchDecision(const kauth_cred_t credential,
|
||||
const vfs_context_t vfs_context,
|
||||
const vnode_t vnode);
|
||||
|
||||
/// Posts the requested message to the client data queue.
|
||||
bool PostToQueue(santa_message_t);
|
||||
|
||||
/// Fetches the vnode_id for a given vnode.
|
||||
uint64_t GetVnodeIDForVnode(const vfs_context_t context, const vnode_t vp);
|
||||
|
||||
/// Returns the current system uptime in microseconds
|
||||
static uint64_t GetCurrentUptime();
|
||||
|
||||
/// Increments the count of active vnode callback's pending.
|
||||
void IncrementListenerInvocations();
|
||||
|
||||
/// Decrements the count of active vnode callback's pending.
|
||||
void DecrementListenerInvocations();
|
||||
|
||||
///
|
||||
/// Vnode Callback
|
||||
/// @param cred The kauth credential for this request.
|
||||
/// @param ctx The VFS context for this request.
|
||||
/// @param vp The Vnode for this request.
|
||||
/// @param errno A pointer to return an errno style error.
|
||||
/// @return int A valid KAUTH_RESULT_*.
|
||||
///
|
||||
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
|
||||
const vnode_t vp, int *errno);
|
||||
///
|
||||
/// FileOp Callback
|
||||
/// @param vp The Vnode for this request.
|
||||
/// @return int Should always be KAUTH_RESULT_DEFER.
|
||||
///
|
||||
int FileOpCallback(const vnode_t vp);
|
||||
|
||||
protected:
|
||||
///
|
||||
/// The maximum number of milliseconds a cached deny message should be
|
||||
@@ -131,12 +124,6 @@ class SantaDecisionManager : public OSObject {
|
||||
///
|
||||
const int kRequestLoopSleepMilliseconds = 10;
|
||||
|
||||
///
|
||||
/// While waiting for a response from the daemon, this is the maximum number
|
||||
/// of loops to wait before sending the request again.
|
||||
///
|
||||
const int kMaxRequestLoops = 50;
|
||||
|
||||
///
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
///
|
||||
@@ -153,20 +140,69 @@ class SantaDecisionManager : public OSObject {
|
||||
///
|
||||
const int kMaxQueueEvents = 512;
|
||||
|
||||
/// Fetches a response from the cache, first checking to see if the
|
||||
/// entry has expired.
|
||||
santa_action_t GetFromCache(const char *identifier);
|
||||
|
||||
/// Fetches a response from the daemon. Handles both daemon death
|
||||
/// and failure to post messages to the daemon.
|
||||
///
|
||||
/// @param message The message to send to the daemon
|
||||
/// @param identifier The vnode ID string for this request
|
||||
/// @return santa_action_t The response for this request
|
||||
///
|
||||
santa_action_t GetFromDaemon(const santa_message_t message,
|
||||
const char *identifier);
|
||||
|
||||
///
|
||||
/// Fetches an execution decision for a file, first using the cache and then
|
||||
/// by sending a message to the daemon and waiting until a response arrives.
|
||||
/// If a daemon isn't connected, will allow execution and cache, logging
|
||||
/// the path to the executed file.
|
||||
///
|
||||
/// @param cred The credential for this request.
|
||||
/// @param vp The Vnode for this request.
|
||||
/// @param vnode_id The ID for this vnode.
|
||||
/// @param vnode_id_str A string representation of the above ID.
|
||||
///
|
||||
santa_action_t FetchDecision(const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str);
|
||||
|
||||
///
|
||||
/// Posts the requested message to the client data queue.
|
||||
///
|
||||
/// @param message The message to send
|
||||
/// @return bool true if sending was successful.
|
||||
///
|
||||
bool PostToQueue(santa_message_t message);
|
||||
|
||||
///
|
||||
/// Fetches the vnode_id for a given vnode.
|
||||
///
|
||||
/// @param ctx The VFS context to use.
|
||||
/// @param vp The Vnode to get the ID for
|
||||
/// @return uint64_t The Vnode ID as a 64-bit unsigned int.
|
||||
///
|
||||
uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp);
|
||||
|
||||
/// Returns the current system uptime in microseconds
|
||||
static uint64_t GetCurrentUptime();
|
||||
|
||||
private:
|
||||
lck_grp_t *sdm_lock_grp_;
|
||||
lck_rw_t *cached_decisions_lock_;
|
||||
lck_mtx_t *dataqueue_lock_;
|
||||
|
||||
OSDictionary *cached_decisions_;
|
||||
|
||||
IOSharedDataQueue *dataqueue_;
|
||||
IOMemoryDescriptor *shared_memory_;
|
||||
|
||||
SInt32 failed_queue_requests_;
|
||||
|
||||
SInt32 listener_invocations_;
|
||||
|
||||
pid_t client_pid_;
|
||||
proc_t client_proc_;
|
||||
|
||||
kauth_listener_t vnode_listener_;
|
||||
kauth_listener_t fileop_listener_;
|
||||
|
||||
@@ -55,7 +55,7 @@ REGISTER_COMMAND_NAME(@"binaryinfo")
|
||||
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
|
||||
if (!fileInfo) {
|
||||
printf("Invalid file\n");
|
||||
printf("Invalid or empty file\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf(" %-25s | %lld\n", "Kernel cache count", cacheCount);
|
||||
|
||||
// Database counts
|
||||
__block int64_t eventCount = 1, binaryRuleCount = -1, certRuleCount = -1;
|
||||
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
|
||||
[[daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
|
||||
binaryRuleCount = binary;
|
||||
certRuleCount = certificate;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
/// If set, this is the user-agent to send with requests, otherwise remains the default
|
||||
/// CFNetwork-based name.
|
||||
///
|
||||
@property(nonatomic) NSString *userAgent;
|
||||
@property(copy, nonatomic) NSString *userAgent;
|
||||
|
||||
///
|
||||
/// If set to YES, this session refuses redirect requests. Defaults to NO.
|
||||
@@ -38,24 +38,24 @@
|
||||
/// If set, the server that we connect to _must_ match this string. Redirects to other
|
||||
/// hosts will not be allowed.
|
||||
///
|
||||
@property(nonatomic) NSString *serverHostname;
|
||||
@property(copy, nonatomic) NSString *serverHostname;
|
||||
|
||||
///
|
||||
/// This should be PEM data containing one or more certificates to use to verify the server's
|
||||
/// certificate chain. This will override the trusted roots in the System Roots.
|
||||
///
|
||||
@property(nonatomic) NSData *serverRootsPemData;
|
||||
@property(copy, nonatomic) NSData *serverRootsPemData;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, the pkcs#12 file will be loaded
|
||||
///
|
||||
@property(nonatomic) NSString *clientCertFile;
|
||||
@property(copy, nonatomic) NSString *clientCertFile;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, the password being used for
|
||||
/// loading the clientCertFile
|
||||
///
|
||||
@property(nonatomic) NSString *clientCertPassword;
|
||||
@property(copy, nonatomic) NSString *clientCertPassword;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, will search the keychain for a
|
||||
@@ -65,7 +65,7 @@
|
||||
/// @note If this property is not set and neither is |clientCertIssuerCn|, the allowed issuers
|
||||
/// provided by the server will be used to find a matching certificate.
|
||||
///
|
||||
@property(nonatomic) NSString *clientCertCommonName;
|
||||
@property(copy, nonatomic) NSString *clientCertCommonName;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, will search the keychain for a
|
||||
@@ -76,7 +76,7 @@
|
||||
/// @note If this property is not set and neither is |clientCertCommonName|, the allowed issuers
|
||||
/// provided by the server will be used to find a matching certificate.
|
||||
///
|
||||
@property(nonatomic) NSString *clientCertIssuerCn;
|
||||
@property(copy, nonatomic) NSString *clientCertIssuerCn;
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
|
||||
|
||||
@@ -112,6 +112,8 @@
|
||||
newRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler {
|
||||
if (self.refusesRedirects) {
|
||||
LOGD(@"Rejected redirection to: %@", request.URL);
|
||||
[task cancel]; // without this, the connection hangs until timeout!?!
|
||||
completionHandler(NULL);
|
||||
} else {
|
||||
completionHandler(request);
|
||||
|
||||
@@ -46,7 +46,7 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Synchronizes Santa with the server.";
|
||||
return @"Synchronizes Santa with a configured server.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
@@ -168,11 +168,11 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Log upload complete");
|
||||
[self eventUpload];
|
||||
} else {
|
||||
LOGE(@"Log upload failed, aborting run");
|
||||
exit(1);
|
||||
LOGE(@"Log upload failed, continuing anyway");
|
||||
}
|
||||
[self eventUpload];
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -198,11 +198,11 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
daemonConn:self.daemonConn
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Event upload complete");
|
||||
exit(0);
|
||||
LOGI(@"Event upload complete");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGW(@"Event upload failed");
|
||||
exit(1);
|
||||
LOGW(@"Event upload failed");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ extern NSString * const kClientMode;
|
||||
extern NSString * const kClientModeMonitor;
|
||||
extern NSString * const kClientModeLockdown;
|
||||
extern NSString * const kCleanSync;
|
||||
extern NSString * const kWhitelistRegex;
|
||||
|
||||
extern NSString * const kEvents;
|
||||
extern NSString * const kFileSHA256;
|
||||
@@ -78,3 +79,5 @@ extern NSString * const kRuleTypeBinary;
|
||||
extern NSString * const kRuleTypeCertificate;
|
||||
extern NSString * const kRuleCustomMsg;
|
||||
extern NSString * const kCursor;
|
||||
|
||||
extern NSString * const kBackoffInterval;
|
||||
|
||||
@@ -32,6 +32,7 @@ NSString * const kClientMode = @"client_mode";
|
||||
NSString * const kClientModeMonitor = @"MONITOR";
|
||||
NSString * const kClientModeLockdown = @"LOCKDOWN";
|
||||
NSString * const kCleanSync = @"clean_sync";
|
||||
NSString * const kWhitelistRegex = @"whitelist_regex";
|
||||
|
||||
NSString * const kEvents = @"events";
|
||||
NSString * const kFileSHA256 = @"file_sha256";
|
||||
@@ -80,3 +81,5 @@ NSString * const kRuleTypeBinary = @"BINARY";
|
||||
NSString * const kRuleTypeCertificate = @"CERTIFICATE";
|
||||
NSString * const kRuleCustomMsg = @"custom_msg";
|
||||
NSString * const kCursor = @"cursor";
|
||||
|
||||
NSString * const kBackoffInterval = @"backoff";
|
||||
|
||||
@@ -104,12 +104,12 @@
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %d %@",
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
handler(NO);
|
||||
} else {
|
||||
LOGI(@"Uploaded %d events", eventIds.count);
|
||||
LOGI(@"Uploaded %lu events", eventIds.count);
|
||||
|
||||
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:eventIds];
|
||||
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %d %@",
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
handler(NO);
|
||||
} else {
|
||||
LOGI(@"Uploaded %d logs", [logsToUpload count]);
|
||||
LOGI(@"Uploaded %lu logs", [logsToUpload count]);
|
||||
handler(YES);
|
||||
}
|
||||
}] resume];
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@implementation SNTCommandSyncPostflight
|
||||
|
||||
@@ -33,15 +35,22 @@
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %d %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
handler(NO);
|
||||
} else {
|
||||
handler(YES);
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
handler(NO);
|
||||
} else {
|
||||
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
|
||||
NSString *backoffInterval = r[kBackoffInterval];
|
||||
if (backoffInterval) {
|
||||
[[daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue] reply:^{}];
|
||||
}
|
||||
|
||||
handler(YES);
|
||||
}
|
||||
}] resume];
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %d %@",
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
handler(NO);
|
||||
@@ -73,9 +73,12 @@
|
||||
[[daemonConn remoteObjectProxy] setClientMode:CLIENTMODE_LOCKDOWN reply:^{}];
|
||||
}
|
||||
|
||||
if ([r[kWhitelistRegex] isKindOfClass:[NSString class]]) {
|
||||
[[daemonConn remoteObjectProxy] setWhitelistPathRegex:r[kWhitelistRegex] reply:^{}];
|
||||
}
|
||||
|
||||
if ([r[kCleanSync] boolValue]) {
|
||||
syncState.cleanSync = YES;
|
||||
LOGD(@"Clean sync requested by server");
|
||||
}
|
||||
|
||||
handler(YES);
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %d %@",
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
handler(NO);
|
||||
@@ -91,7 +91,7 @@
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRules:syncState.downloadedRules
|
||||
cleanSlate:syncState.cleanSync
|
||||
reply:^{
|
||||
LOGI(@"Added %d rule(s)", syncState.downloadedRules.count);
|
||||
LOGI(@"Added %lu rule(s)", syncState.downloadedRules.count);
|
||||
handler(YES);
|
||||
}];
|
||||
} else {
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
@property NSURL *syncBaseURL;
|
||||
|
||||
/// Machine identifier and owner
|
||||
@property NSString *machineID;
|
||||
@property NSString *machineOwner;
|
||||
@property(copy) NSString *machineID;
|
||||
@property(copy) NSString *machineOwner;
|
||||
|
||||
/// Clean sync flag, sent from server. If True, all existing rules
|
||||
/// should be deleted before inserting any new rules.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <IOKit/kext/KextManager.h>
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
@@ -61,8 +62,7 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
return loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
SNTFileInfo *driverInfo =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/Library/Extensions/santa-driver.kext"];
|
||||
SNTFileInfo *driverInfo = [[SNTFileInfo alloc] initWithPath:@(kKextPath)];
|
||||
if (driverInfo) {
|
||||
return [driverInfo.bundleVersion stringByAppendingString:@" (unloaded)"];
|
||||
}
|
||||
@@ -71,13 +71,13 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
}
|
||||
|
||||
+ (NSString *)santadVersion {
|
||||
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@"/usr/libexec/santad"];
|
||||
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaDPath)];
|
||||
return daemonInfo.bundleVersion;
|
||||
}
|
||||
|
||||
+ (NSString *)santaAppVersion {
|
||||
SNTFileInfo *guiInfo =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/Applications/Santa.app/Contents/MacOS/Santa"];
|
||||
[[SNTFileInfo alloc] initWithPath:@"/Applications/Santa.app/Contents/MacOS/Santa"];
|
||||
return guiInfo.bundleVersion;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -130,17 +129,7 @@
|
||||
// Validate the binary aynchronously on a concurrent queue so we don't
|
||||
// hold up other execution requests in the background.
|
||||
dispatch_async(q, ^{
|
||||
struct passwd *user = getpwuid(message.userId);
|
||||
NSString *userName;
|
||||
if (user) {
|
||||
userName = @(user->pw_name);
|
||||
}
|
||||
|
||||
[self.execController validateBinaryWithPath:@(message.path)
|
||||
userName:userName
|
||||
pid:@(message.pid)
|
||||
ppid:@(message.ppid)
|
||||
vnodeId:message.vnode_id];
|
||||
[self.execController validateBinaryWithMessage:message];
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -17,21 +17,64 @@
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
|
||||
@interface SNTDaemonControlController ()
|
||||
@property dispatch_source_t syncTimer;
|
||||
@end
|
||||
|
||||
@implementation SNTDaemonControlController
|
||||
|
||||
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_driverManager = driverManager;
|
||||
|
||||
_syncTimer = [self createSyncTimer];
|
||||
[self rescheduleSyncSecondsFromNow:600];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_source_t)createSyncTimer {
|
||||
dispatch_source_t syncTimerQ = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
|
||||
dispatch_source_set_event_handler(syncTimerQ, ^{
|
||||
[self rescheduleSyncSecondsFromNow:600];
|
||||
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[[SNTConfigurator configurator] setSyncBackOff:NO];
|
||||
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
fclose(stdout);
|
||||
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", NULL));
|
||||
}
|
||||
});
|
||||
|
||||
dispatch_resume(syncTimerQ);
|
||||
|
||||
return syncTimerQ;
|
||||
}
|
||||
|
||||
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
|
||||
uint64_t interval = seconds * NSEC_PER_SEC;
|
||||
uint64_t leeway = (seconds * 0.05) * NSEC_PER_SEC;
|
||||
dispatch_source_set_timer(self.syncTimer, dispatch_walltime(NULL, interval), interval, leeway);
|
||||
}
|
||||
|
||||
#pragma mark Kernel ops
|
||||
|
||||
- (void)cacheCount:(void (^)(int64_t))reply {
|
||||
@@ -94,4 +137,18 @@
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
|
||||
[self rescheduleSyncSecondsFromNow:seconds];
|
||||
[[SNTConfigurator configurator] setSyncBackOff:YES];
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply {
|
||||
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
|
||||
options:0
|
||||
error:NULL];
|
||||
[[SNTConfigurator configurator] setWhitelistPathRegex:re];
|
||||
reply();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -126,6 +126,9 @@
|
||||
for (NSNumber *index in indexes) {
|
||||
[self deleteEventWithId:index];
|
||||
}
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
[db executeUpdate:@"VACUUM"];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
@class SNTCodesignChecker;
|
||||
@class SNTDriverManager;
|
||||
@@ -48,16 +49,8 @@
|
||||
/// the kernel, logs the event to the log and if necessary stores the event in the database and
|
||||
/// sends a notification to the GUI agent.
|
||||
///
|
||||
/// @param path the binary that's being executed
|
||||
/// @param userName the user who's executing the binary
|
||||
/// @param pid the process id being executed
|
||||
/// @param ppid the parent process id
|
||||
/// @param vnodeId the id of the vnode being executed
|
||||
/// @param message The message sent from the kernel.
|
||||
///
|
||||
- (void)validateBinaryWithPath:(NSString *)path
|
||||
userName:(NSString *)userName
|
||||
pid:(NSNumber *)pid
|
||||
ppid:(NSNumber *)ppid
|
||||
vnodeId:(uint64_t)vnodeId;
|
||||
- (void)validateBinaryWithMessage:(santa_message_t)message;
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
#import "SNTExecutionController.h"
|
||||
|
||||
#include <libproc.h>
|
||||
#include <pwd.h>
|
||||
#include <utmpx.h>
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
#import "SNTCodesignChecker.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
@@ -46,7 +48,6 @@
|
||||
_ruleTable = ruleTable;
|
||||
_eventTable = eventTable;
|
||||
_notifierConnection = notifier;
|
||||
LOGI(@"Log format: Decision (A|D), Reason (B|C|S|?), SHA-256, Path, Cert SHA-256, Cert CN");
|
||||
|
||||
// Workaround for xpcproxy/libsecurity bug on Yosemite
|
||||
// This establishes the XPC connection between libsecurity and syspolicyd.
|
||||
@@ -58,21 +59,29 @@
|
||||
|
||||
#pragma mark Binary Validation
|
||||
|
||||
- (void)validateBinaryWithPath:(NSString *)path
|
||||
userName:(NSString *)userName
|
||||
pid:(NSNumber *)pid
|
||||
ppid:(NSNumber *)ppid
|
||||
vnodeId:(uint64_t)vnodeId {
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:path];
|
||||
- (void)validateBinaryWithMessage:(santa_message_t)message {
|
||||
NSString *path = @(message.path);
|
||||
uint64_t vnodeId = message.vnode_id;
|
||||
|
||||
NSError *fileInfoError;
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:path error:&fileInfoError];
|
||||
NSString *sha256 = [binInfo SHA256];
|
||||
|
||||
// If we can't read the file and hash properly, log an error.
|
||||
if (!binInfo || !sha256) {
|
||||
LOGW(@"Failed to read file %@: %@", path, fileInfoError.localizedDescription);
|
||||
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
|
||||
[self logDecisionForEventState:EVENTSTATE_ALLOW_UNKNOWN sha256:nil path:path leafCert:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
// These will be filled in either in later steps
|
||||
santa_action_t respondedAction = ACTION_UNSET;
|
||||
SNTRule *rule;
|
||||
|
||||
// Get name of parent process. Do this before responding to be sure parent doesn't go away.
|
||||
char pname[PROC_PIDPATHINFO_MAXSIZE];
|
||||
proc_name([ppid intValue], pname, PROC_PIDPATHINFO_MAXSIZE);
|
||||
proc_name(message.ppid, pname, PROC_PIDPATHINFO_MAXSIZE);
|
||||
|
||||
// Step 1 - binary rule?
|
||||
rule = [self.ruleTable binaryRuleForSHA256:sha256];
|
||||
@@ -93,7 +102,7 @@
|
||||
}
|
||||
|
||||
// Step 3 - in scope?
|
||||
if (![self fileIsInScope:path]) {
|
||||
if (!rule && ![self fileIsInScope:path]) {
|
||||
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
|
||||
[self logDecisionForEventState:EVENTSTATE_ALLOW_SCOPE sha256:sha256 path:path leafCert:nil];
|
||||
return;
|
||||
@@ -124,13 +133,20 @@
|
||||
}
|
||||
|
||||
se.signingChain = csInfo.certificates;
|
||||
se.executingUser = userName;
|
||||
se.occurrenceDate = [[NSDate alloc] init];
|
||||
se.decision = [self eventStateForDecision:respondedAction type:rule.type];
|
||||
se.pid = pid;
|
||||
se.ppid = ppid;
|
||||
se.pid = @(message.pid);
|
||||
se.ppid = @(message.ppid);
|
||||
se.parentName = @(pname);
|
||||
|
||||
struct passwd *user = getpwuid(message.userId);
|
||||
endpwent();
|
||||
NSString *userName;
|
||||
if (user) {
|
||||
userName = @(user->pw_name);
|
||||
}
|
||||
se.executingUser = userName;
|
||||
|
||||
NSArray *loggedInUsers, *currentSessions;
|
||||
[self loggedInUsers:&loggedInUsers sessions:¤tSessions];
|
||||
se.currentSessions = currentSessions;
|
||||
@@ -141,9 +157,11 @@
|
||||
if (respondedAction == ACTION_RESPOND_CHECKBW_DENY) {
|
||||
// So the server has something to show the user straight away, initiate an event
|
||||
// upload for the blocked binary rather than waiting for the next sync.
|
||||
// The event upload is skipped if the full path is equal to that of /usr/sbin/santactl so that
|
||||
// The event upload is skipped if the full path is equal to that of santactl so that
|
||||
/// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
|
||||
if (![path isEqual:@"/usr/sbin/santactl"]) {
|
||||
if (![path isEqual:@(kSantaCtlPath)] &&
|
||||
[[SNTConfigurator configurator] syncBaseURL]
|
||||
&& ![[SNTConfigurator configurator] syncBackOff]) {
|
||||
[self initiateEventUploadForSHA256:sha256];
|
||||
}
|
||||
|
||||
@@ -166,13 +184,14 @@
|
||||
///
|
||||
/// Files that are out of scope:
|
||||
/// + Non Mach-O files that are not part of an installer package.
|
||||
/// + Files in whitelisted directories.
|
||||
/// + Files in whitelisted path.
|
||||
///
|
||||
/// @return @c YES if file is in scope, @c NO otherwise.
|
||||
///
|
||||
- (BOOL)fileIsInScope:(NSString *)path {
|
||||
// Determine if file is within a whitelisted directory.
|
||||
if ([self pathIsInWhitelistedDir:path]) {
|
||||
// Determine if file is within a whitelisted path
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
|
||||
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -187,30 +206,22 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)pathIsInWhitelistedDir:(NSString *)path {
|
||||
// TODO(rah): Implement this.
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (santa_eventstate_t)eventStateForDecision:(santa_action_t)decision type:(santa_ruletype_t)type {
|
||||
if (decision == ACTION_RESPOND_CHECKBW_ALLOW) {
|
||||
if (type == RULETYPE_BINARY) {
|
||||
return EVENTSTATE_ALLOW_BINARY;
|
||||
} else if (type == RULETYPE_CERT) {
|
||||
return EVENTSTATE_ALLOW_CERTIFICATE;
|
||||
} else {
|
||||
return EVENTSTATE_ALLOW_UNKNOWN;
|
||||
}
|
||||
} else if (decision == ACTION_RESPOND_CHECKBW_DENY) {
|
||||
if (type == RULETYPE_BINARY) {
|
||||
return EVENTSTATE_BLOCK_BINARY;
|
||||
} else if (decision == RULETYPE_CERT) {
|
||||
return EVENTSTATE_BLOCK_CERTIFICATE;
|
||||
} else {
|
||||
return EVENTSTATE_BLOCK_UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
return EVENTSTATE_UNKNOWN;
|
||||
switch (decision) {
|
||||
case ACTION_RESPOND_CHECKBW_ALLOW:
|
||||
switch (type) {
|
||||
case RULETYPE_BINARY: return EVENTSTATE_ALLOW_BINARY;
|
||||
case RULETYPE_CERT: return EVENTSTATE_ALLOW_CERTIFICATE;
|
||||
default: return EVENTSTATE_ALLOW_UNKNOWN;
|
||||
|
||||
}
|
||||
case ACTION_RESPOND_CHECKBW_DENY:
|
||||
switch (type) {
|
||||
case RULETYPE_BINARY: return EVENTSTATE_BLOCK_BINARY;
|
||||
case RULETYPE_CERT: return EVENTSTATE_BLOCK_CERTIFICATE;
|
||||
default: return EVENTSTATE_BLOCK_UNKNOWN;
|
||||
}
|
||||
default: return EVENTSTATE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +269,6 @@
|
||||
}
|
||||
|
||||
- (void)initiateEventUploadForSHA256:(NSString *)sha256 {
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
fclose(stdout);
|
||||
@@ -266,11 +276,10 @@
|
||||
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
exit(1);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
exit(execl("/usr/sbin/santactl", "/usr/sbin/santactl", "sync",
|
||||
"singleevent", [sha256 UTF8String], NULL));
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "singleevent", [sha256 UTF8String], NULL));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,11 +304,10 @@
|
||||
}
|
||||
|
||||
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
|
||||
struct utmpx *nxt;
|
||||
|
||||
NSMutableDictionary *loggedInUsers = [[NSMutableDictionary alloc] init];
|
||||
NSMutableDictionary *loggedInHosts = [[NSMutableDictionary alloc] init];
|
||||
|
||||
struct utmpx *nxt;
|
||||
while ((nxt = getutxent())) {
|
||||
if (nxt->ut_type != USER_PROCESS) continue;
|
||||
|
||||
@@ -312,11 +320,11 @@
|
||||
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_line];
|
||||
}
|
||||
|
||||
if ([userName length] > 0) {
|
||||
if (userName.length > 0) {
|
||||
loggedInUsers[userName] = [NSNull null];
|
||||
}
|
||||
|
||||
if ([sessionName length] > 1) {
|
||||
if (sessionName.length > 1) {
|
||||
loggedInHosts[sessionName] = [NSNull null];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,71 @@
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#include <pthread/pthread.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#import "SNTApplication.h"
|
||||
|
||||
/// Converts a timeval struct to double, converting the microseconds value to seconds.
|
||||
static inline double timeval_to_double(struct timeval tv) {
|
||||
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
|
||||
}
|
||||
|
||||
/// The watchdog thread function, used to monitor santad CPU/RAM usage and print a warning
|
||||
/// if it goes over certain thresholds.
|
||||
void *watchdogThreadFunction(__unused void *idata) {
|
||||
pthread_setname_np("com.google.santa.watchdog");
|
||||
|
||||
// Number of seconds to wait between checks.
|
||||
const int timeInterval = 60;
|
||||
|
||||
// Amount of CPU usage to trigger warning, as a percentage averaged over timeInterval
|
||||
// santad's usual CPU usage is 0-3% but can occasionally spike if lots of processes start at once.
|
||||
const int cpuWarnThreshold = 20.0;
|
||||
|
||||
// Amount of RAM usage to trigger warning, in MB.
|
||||
// santad's usual RAM usage is between 5-50MB but can spike if lots of processes start at once.
|
||||
const int memWarnThreshold = 250;
|
||||
|
||||
double prevTotalTime = 0.0;
|
||||
double prevRamUseMB = 0.0;
|
||||
struct rusage usage;
|
||||
struct mach_task_basic_info taskInfo;
|
||||
mach_msg_type_number_t taskInfoCount = MACH_TASK_BASIC_INFO_COUNT;
|
||||
|
||||
while(true) {
|
||||
@autoreleasepool {
|
||||
sleep(timeInterval);
|
||||
|
||||
// CPU
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
double totalTime = timeval_to_double(usage.ru_utime) + timeval_to_double(usage.ru_stime);
|
||||
double percentage = (((totalTime - prevTotalTime) / (double)timeInterval) * 100.0);
|
||||
prevTotalTime = totalTime;
|
||||
|
||||
if (percentage > cpuWarnThreshold) {
|
||||
LOGW(@"Watchdog: potentially high CPU use, ~%.2f%% over last %d seconds.",
|
||||
percentage, timeInterval);
|
||||
}
|
||||
|
||||
// RAM
|
||||
if (KERN_SUCCESS == task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
|
||||
(task_info_t)&taskInfo, &taskInfoCount)) {
|
||||
double ramUseMB = (double) taskInfo.resident_size / 1024 / 1024;
|
||||
if (ramUseMB > memWarnThreshold && ramUseMB > prevRamUseMB) {
|
||||
LOGW(@"Watchdog: potentially high RAM use, RSS is %.2fMB.", ramUseMB);
|
||||
}
|
||||
prevRamUseMB = ramUseMB;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
@autoreleasepool {
|
||||
// Do not buffer stdout
|
||||
setbuf(stdout, NULL);
|
||||
// Do not wait on child processes
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
|
||||
|
||||
@@ -28,11 +87,19 @@ int main(int argc, const char *argv[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Close stdout/stderr so logging goes to syslog
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
|
||||
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);
|
||||
|
||||
SNTApplication *s = [[SNTApplication alloc] init];
|
||||
[s performSelectorInBackground:@selector(run) withObject:nil];
|
||||
|
||||
// Create watchdog thread
|
||||
pthread_t watchdogThread;
|
||||
pthread_create(&watchdogThread, NULL, watchdogThreadFunction, NULL);
|
||||
|
||||
[[NSRunLoop mainRunLoop] run];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@
|
||||
|
||||
#pragma mark - Driver Helpers
|
||||
|
||||
/// Call in-kernel function: |kSantaUserClientReceive| passing the |action| and |vnodeId| via a
|
||||
/// |santa_message_t| struct.
|
||||
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
|
||||
/// passing the |vnodeID|.
|
||||
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeid {
|
||||
if (action == ACTION_RESPOND_CHECKBW_ALLOW) {
|
||||
IOConnectCallScalarMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid, 1, 0, 0);
|
||||
@@ -305,7 +305,6 @@
|
||||
fputc(ch, outfile);
|
||||
}
|
||||
fclose(infile);
|
||||
fclose(outfile);
|
||||
|
||||
// Now try running the temp file again. If it succeeds, the test failed.
|
||||
NSTask *ps = [self taskWithPath:@"santakerneltests_tmp"];
|
||||
@@ -313,7 +312,22 @@
|
||||
@try {
|
||||
[ps launch];
|
||||
[ps waitUntilExit];
|
||||
TFAIL();
|
||||
TFAILINFO("Launched after write while file open");
|
||||
[fm removeItemAtPath:@"santakerneltests_tmp" error:nil];
|
||||
} @catch (NSException *exception) {
|
||||
// This is a pass, but we have more to do.
|
||||
}
|
||||
|
||||
// Close the file to flush the write.
|
||||
fclose(outfile);
|
||||
|
||||
// And try running the temp file again. If it succeeds, the test failed.
|
||||
ps = [self taskWithPath:@"santakerneltests_tmp"];
|
||||
|
||||
@try {
|
||||
[ps launch];
|
||||
[ps waitUntilExit];
|
||||
TFAILINFO("Launched after file closed");
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
} @finally {
|
||||
@@ -358,12 +372,12 @@
|
||||
} else if (pid > 0) {
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == EACCES) {
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
|
||||
TPASS();
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
TFAILINFO("Process was executed and is waiting for debugger");
|
||||
} else {
|
||||
TFAILINFO("Process did not exit with EACCESS as expected");
|
||||
TFAILINFO("Process did not exit with EPERM as expected");
|
||||
}
|
||||
} else if (pid == 0) {
|
||||
fclose(stdout);
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
|
||||
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
|
||||
OCMStub([self.mockFileInfo alloc]).andReturn(self.mockFileInfo);
|
||||
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY]).andReturn(self.mockFileInfo);
|
||||
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:nil]]).andReturn(self.mockFileInfo);
|
||||
|
||||
self.mockRuleDatabase = OCMClassMock([SNTRuleTable class]);
|
||||
self.mockEventDatabase = OCMClassMock([SNTEventTable class]);
|
||||
@@ -72,6 +73,16 @@
|
||||
notifierConnection:nil];
|
||||
}
|
||||
|
||||
/// Return a pre-configured santa_message_ t for testing with.
|
||||
- (santa_message_t)getMessage {
|
||||
santa_message_t message = {0};
|
||||
message.pid = 12;
|
||||
message.ppid = 1;
|
||||
message.vnode_id = 1234;
|
||||
strncpy(message.path, "/a/file", 7);
|
||||
return message;
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
[self.mockFileInfo stopMocking];
|
||||
[self.mockCodesignChecker stopMocking];
|
||||
@@ -92,11 +103,7 @@
|
||||
rule.state = RULESTATE_WHITELIST;
|
||||
OCMExpect([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(12)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:1234]);
|
||||
@@ -112,11 +119,7 @@
|
||||
rule.state = RULESTATE_BLACKLIST;
|
||||
OCMExpect([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(12)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
|
||||
forVnodeID:1234]);
|
||||
@@ -134,11 +137,7 @@
|
||||
rule.state = RULESTATE_WHITELIST;
|
||||
OCMExpect([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(12)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:1234]);
|
||||
@@ -148,6 +147,8 @@
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
|
||||
|
||||
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
id cert = OCMClassMock([SNTCertificate class]);
|
||||
OCMExpect([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
OCMExpect([cert SHA256]).andReturn(@"a");
|
||||
@@ -156,11 +157,7 @@
|
||||
rule.state = RULESTATE_BLACKLIST;
|
||||
OCMExpect([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(12)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
|
||||
forVnodeID:1234]);
|
||||
@@ -170,21 +167,15 @@
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
|
||||
|
||||
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_MONITOR);
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(12)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:1234]);
|
||||
|
||||
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(12)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
|
||||
forVnodeID:1234]);
|
||||
}
|
||||
@@ -194,13 +185,16 @@
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(NO);
|
||||
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(24)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:1234]);
|
||||
}
|
||||
|
||||
- (void)testMissingShasum {
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:1234]);
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.path, @"/Applications/Safari.app/Contents/MacOS/Safari");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../bin/ls"];
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
|
||||
XCTAssertEqualObjects(sut.path, @"/bin/ls");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/qlmanage"];
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
[super setUp];
|
||||
|
||||
self.fm = [NSFileManager defaultManager];
|
||||
self.file = @"/tmp/SNTFileWatcherTest_File";
|
||||
self.file = [NSTemporaryDirectory() stringByAppendingString:@"SNTFileWatcherTest_File"];
|
||||
[self createFile];
|
||||
usleep(10000);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user