mirror of
https://github.com/google/santa.git
synced 2026-01-16 09:48:01 -05:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8faf3eec53 | ||
|
|
2bc3df3255 | ||
|
|
5b0e550c85 | ||
|
|
e52211abf2 | ||
|
|
9b6f231b34 | ||
|
|
b71223705f | ||
|
|
863fbe69bb | ||
|
|
2d46279961 | ||
|
|
0d0207d77f | ||
|
|
00bbade34f | ||
|
|
682f741ddc | ||
|
|
3d2744c9e3 | ||
|
|
cc286dcf16 | ||
|
|
27c6e2a7bd | ||
|
|
72c7a67ad5 | ||
|
|
8fe5e4e238 | ||
|
|
02f23d0c62 | ||
|
|
ff6f4d4152 | ||
|
|
2242f46792 | ||
|
|
642b5609b2 | ||
|
|
98878f3e7c | ||
|
|
3eb28deccf | ||
|
|
761a852156 | ||
|
|
f4ddb11c1f | ||
|
|
75158c11ea | ||
|
|
fe96706b0c | ||
|
|
b87482e824 | ||
|
|
a9ba99dc79 | ||
|
|
8884e92a1a | ||
|
|
6385514257 | ||
|
|
d3ad47022b | ||
|
|
138d4b507d | ||
|
|
3c0b195bcf | ||
|
|
d941a71bb5 | ||
|
|
08697d9daf | ||
|
|
8959871988 | ||
|
|
bb43a04992 | ||
|
|
5f93dc7991 | ||
|
|
9be8eb223c | ||
|
|
e8b6c47e0f | ||
|
|
697d442afb | ||
|
|
5dbd261b5a | ||
|
|
9bc94ca658 | ||
|
|
4404b5f849 | ||
|
|
6a4b73b8a9 | ||
|
|
b6146224b3 | ||
|
|
e3593c1b0c | ||
|
|
90a2f10da6 | ||
|
|
60bab1c004 | ||
|
|
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 |
@@ -1,5 +1,6 @@
|
||||
---
|
||||
language: objective-c
|
||||
cache: cocoapods
|
||||
|
||||
before_install:
|
||||
- gem install cocoapods xcpretty
|
||||
|
||||
@@ -25,41 +25,32 @@ PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/rele
|
||||
# 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
|
||||
# |-- 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:
|
||||
@@ -68,12 +59,6 @@ download:
|
||||
@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
|
||||
@@ -92,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 -sf /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)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
|
||||
? [= Sender kernel] [S= Message santa-driver:] claim
|
||||
? [= Sender kernel] [S= Message santa-driver:] file /var/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.2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Santa [](https://travis-ci.org/google/santa)
|
||||
=====
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for Mac OS X. It consists of
|
||||
Santa is a binary whitelisting/blacklisting system for OS X. It consists of
|
||||
a kernel extension that monitors for executions, a userland daemon that makes
|
||||
execution decisions based on the contents of a SQLite database, a GUI agent that
|
||||
notifies the user in case of a block decision and a command-line utility for
|
||||
|
||||
44
Rakefile
44
Rakefile
@@ -1,10 +1,9 @@
|
||||
require 'timeout'
|
||||
|
||||
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']
|
||||
DSYMS = ['Santa.app.dSYM', 'santa-driver.kext.dSYM', 'santad.dSYM', 'santactl.dSYM']
|
||||
XCPRETTY_DEFAULTS = '-sc'
|
||||
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
|
||||
@@ -33,10 +32,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 +79,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
|
||||
|
||||
@@ -99,6 +93,7 @@ end
|
||||
task :dist do
|
||||
desc "Create distribution folder"
|
||||
|
||||
Rake::Task['clean'].invoke()
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
@@ -109,9 +104,13 @@ task :dist do
|
||||
|
||||
BINARIES.each do |x|
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}", "#{DIST_PATH}/binaries")
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}.dSYM", "#{DIST_PATH}/dsym")
|
||||
end
|
||||
|
||||
DSYMS.each do |x|
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}", "#{DIST_PATH}/dsym")
|
||||
end
|
||||
|
||||
|
||||
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{DIST_PATH}/conf")}
|
||||
|
||||
puts "Distribution folder created"
|
||||
@@ -130,16 +129,17 @@ namespace :tests do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:debug'].invoke()
|
||||
Rake::Task['load_kext'].invoke
|
||||
timeout = 30
|
||||
puts "Running kernel tests with a #{timeout} second timeout"
|
||||
FileUtils.mkdir_p("/tmp/santa_kerneltests_tmp")
|
||||
begin
|
||||
Timeout::timeout(timeout) {
|
||||
system "sudo #{OUTPUT_PATH}/Products/Debug/KernelTests"
|
||||
}
|
||||
rescue Timeout::Error
|
||||
puts "ERROR: tests ran for longer than #{timeout} seconds and were killed."
|
||||
puts "\033[?25l\033[12h" # hide cursor
|
||||
puts "Running kernel tests"
|
||||
system "cd /tmp/santa_kerneltests_tmp && sudo #{Dir.pwd}/#{OUTPUT_PATH}/Products/Debug/KernelTests"
|
||||
rescue Exception
|
||||
ensure
|
||||
puts "\033[?25h\033[12l\n\n" # unhide cursor
|
||||
FileUtils.rm_rf("/tmp/santa_kerneltests_tmp")
|
||||
Rake::Task['unload_kext'].execute
|
||||
end
|
||||
Rake::Task['unload_kext'].execute
|
||||
end
|
||||
end
|
||||
|
||||
@@ -156,7 +156,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 +169,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"
|
||||
|
||||
@@ -78,6 +78,10 @@
|
||||
0D4644C5182AF81700098690 /* SantaDecisionManager.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0D4644C3182AF81700098690 /* SantaDecisionManager.cc */; };
|
||||
0D4644C6182AF81700098690 /* SantaDecisionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D4644C4182AF81700098690 /* SantaDecisionManager.h */; };
|
||||
0D4A5007176A4602004F63BF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D4A5006176A4602004F63BF /* Security.framework */; };
|
||||
0D536ED71B8E7A2E0039A26D /* bad_pagezero in Resources */ = {isa = PBXBuildFile; fileRef = 0D536ED51B8E7A2E0039A26D /* bad_pagezero */; };
|
||||
0D536ED81B8E7A2E0039A26D /* missing_pagezero in Resources */ = {isa = PBXBuildFile; fileRef = 0D536ED61B8E7A2E0039A26D /* missing_pagezero */; };
|
||||
0D536EDB1B94E9230039A26D /* SNTEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D536EDA1B94E9230039A26D /* SNTEventLog.m */; };
|
||||
0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D536EDA1B94E9230039A26D /* SNTEventLog.m */; };
|
||||
0D54E0B11976F8D3000BB59F /* SNTFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD6041190ACCB8006B445C /* SNTFileInfo.m */; };
|
||||
0D59C0E417710E6000748EBF /* SNTCodesignChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */; };
|
||||
0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */; };
|
||||
@@ -118,6 +122,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 */; };
|
||||
@@ -148,6 +154,8 @@
|
||||
0DE50F6C19130358007B2B0C /* SNTStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604A19105433006B445C /* SNTStoredEvent.m */; };
|
||||
0DE50F6E191304E0007B2B0C /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; };
|
||||
0DE6788D1784A8C2007A9E52 /* SNTExecutionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */; };
|
||||
0DE71A751B95F7F900518526 /* SNTCachedDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE71A741B95F7F900518526 /* SNTCachedDecision.m */; };
|
||||
0DE71A761B95F7F900518526 /* SNTCachedDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE71A741B95F7F900518526 /* SNTCachedDecision.m */; };
|
||||
0DEFB7C01ACB28B000B92AAE /* SNTCommandSyncConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */; };
|
||||
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
|
||||
0DEFB7C51ACDD80100B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
|
||||
@@ -203,8 +211,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; };
|
||||
@@ -266,6 +302,10 @@
|
||||
0D4644C3182AF81700098690 /* SantaDecisionManager.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SantaDecisionManager.cc; sourceTree = "<group>"; };
|
||||
0D4644C4182AF81700098690 /* SantaDecisionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaDecisionManager.h; sourceTree = "<group>"; };
|
||||
0D4A5006176A4602004F63BF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = "<absolute>"; };
|
||||
0D536ED51B8E7A2E0039A26D /* bad_pagezero */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = bad_pagezero; sourceTree = "<group>"; };
|
||||
0D536ED61B8E7A2E0039A26D /* missing_pagezero */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = missing_pagezero; sourceTree = "<group>"; };
|
||||
0D536ED91B94E9230039A26D /* SNTEventLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTEventLog.h; sourceTree = "<group>"; };
|
||||
0D536EDA1B94E9230039A26D /* SNTEventLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTEventLog.m; sourceTree = "<group>"; };
|
||||
0D59C0E217710E6000748EBF /* SNTCodesignChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCodesignChecker.h; sourceTree = "<group>"; };
|
||||
0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCodesignChecker.m; sourceTree = "<group>"; };
|
||||
0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDatabaseController.h; sourceTree = "<group>"; };
|
||||
@@ -332,6 +372,8 @@
|
||||
0DE50F671912716A007B2B0C /* SNTRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTRule.m; sourceTree = "<group>"; };
|
||||
0DE6788B1784A8C2007A9E52 /* SNTExecutionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTExecutionController.h; sourceTree = "<group>"; };
|
||||
0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SNTExecutionController.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
0DE71A731B95F7F900518526 /* SNTCachedDecision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCachedDecision.h; sourceTree = "<group>"; };
|
||||
0DE71A741B95F7F900518526 /* SNTCachedDecision.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCachedDecision.m; sourceTree = "<group>"; };
|
||||
0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncConstants.m; sourceTree = "<group>"; };
|
||||
0DEFB7C11ACB28BC00B92AAE /* SNTCommandSyncConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncConstants.h; sourceTree = "<group>"; };
|
||||
0DEFB7C21ACDD80100B92AAE /* SNTFileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTFileWatcher.h; sourceTree = "<group>"; };
|
||||
@@ -432,6 +474,8 @@
|
||||
0D260DB018B68E12002A0B55 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D536ED51B8E7A2E0039A26D /* bad_pagezero */,
|
||||
0D536ED61B8E7A2E0039A26D /* missing_pagezero */,
|
||||
0D2CD4601A81C7B100C9C910 /* dn.plist */,
|
||||
0D6FDC8618C6913D0044685C /* apple.pem */,
|
||||
0D6FDC8218C68D7E0044685C /* GIAG2.crt */,
|
||||
@@ -658,18 +702,22 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0DA73CA519363C9F0056D7C4 /* DataLayer */,
|
||||
0D3AF83118F87CEF0087BCEE /* Resources */,
|
||||
0D9A7F411759330500035EB5 /* main.m */,
|
||||
0DB8ACBF185662DC00FEF9C7 /* SNTApplication.h */,
|
||||
0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */,
|
||||
0DE71A731B95F7F900518526 /* SNTCachedDecision.h */,
|
||||
0DE71A741B95F7F900518526 /* SNTCachedDecision.m */,
|
||||
0D8E18CB19107B56000F89B8 /* SNTDaemonControlController.h */,
|
||||
0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */,
|
||||
0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */,
|
||||
0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */,
|
||||
0D7D01851774F93A005DBAB4 /* SNTDriverManager.h */,
|
||||
0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */,
|
||||
0D536ED91B94E9230039A26D /* SNTEventLog.h */,
|
||||
0D536EDA1B94E9230039A26D /* SNTEventLog.m */,
|
||||
0DE6788B1784A8C2007A9E52 /* SNTExecutionController.h */,
|
||||
0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */,
|
||||
0D9A7F411759330500035EB5 /* main.m */,
|
||||
0D3AF83118F87CEF0087BCEE /* Resources */,
|
||||
);
|
||||
name = santad;
|
||||
path = Source/santad;
|
||||
@@ -835,10 +883,13 @@
|
||||
0DD98E691A5DD5C900A754C6 /* Update Module Version */,
|
||||
0D91BCAE174E8A7E00131A7D /* Sources */,
|
||||
0D91BCB0174E8A7E00131A7D /* Headers */,
|
||||
0DC765E91B28D9CB00BAE651 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
0DC765E61B28D9C600BAE651 /* PBXTargetDependency */,
|
||||
0DC765E81B28D9C600BAE651 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "santa-driver";
|
||||
productName = "santa-driver";
|
||||
@@ -909,8 +960,10 @@
|
||||
0D6FDC8518C68E500044685C /* GIAG2.pem in Resources */,
|
||||
0D6FDC8318C68D7E0044685C /* GIAG2.crt in Resources */,
|
||||
0D6F12DA19EDE51E006B218E /* tubitak.crt in Resources */,
|
||||
0D536ED71B8E7A2E0039A26D /* bad_pagezero in Resources */,
|
||||
0D2CD4611A81C7B100C9C910 /* dn.plist in Resources */,
|
||||
0D6FDC8718C6913D0044685C /* apple.pem in Resources */,
|
||||
0D536ED81B8E7A2E0039A26D /* missing_pagezero in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1096,6 +1149,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0D88680C1AC48A1400B86659 /* SNTSystemInfo.m in Sources */,
|
||||
0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */,
|
||||
0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */,
|
||||
0D3AFBF018FB4C6C0087BCEE /* SNTDriverManager.m in Sources */,
|
||||
0DCD6044190ACCB8006B445C /* SNTFileInfo.m in Sources */,
|
||||
@@ -1121,6 +1175,7 @@
|
||||
0DC5D86E191AED220078A5C0 /* SNTRuleTable.m in Sources */,
|
||||
0D31DF4718D254B3002B300D /* SNTCodesignChecker.m in Sources */,
|
||||
0DD0D492194F9BEF005F27EB /* SNTLogging.m in Sources */,
|
||||
0DE71A761B95F7F900518526 /* SNTCachedDecision.m in Sources */,
|
||||
0DD0D48D194F6D5B005F27EB /* SNTCodesignCheckerTest.m in Sources */,
|
||||
0DCD605919115E5A006B445C /* SNTXPCNotifierInterface.m in Sources */,
|
||||
0DE50F691912B0CD007B2B0C /* SNTRule.m in Sources */,
|
||||
@@ -1212,6 +1267,7 @@
|
||||
0D9A7F421759330500035EB5 /* main.m in Sources */,
|
||||
0D1AF477187C7A2C00D3298D /* SNTCertificate.m in Sources */,
|
||||
0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */,
|
||||
0DE71A751B95F7F900518526 /* SNTCachedDecision.m in Sources */,
|
||||
0DCD6042190ACCB8006B445C /* SNTFileInfo.m in Sources */,
|
||||
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
|
||||
0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */,
|
||||
@@ -1224,6 +1280,7 @@
|
||||
0D59C0E417710E6000748EBF /* SNTCodesignChecker.m in Sources */,
|
||||
0D42D2B819D2042900955F08 /* SNTConfigurator.m in Sources */,
|
||||
0DCD605519115D17006B445C /* SNTXPCControlInterface.m in Sources */,
|
||||
0D536EDB1B94E9230039A26D /* SNTEventLog.m in Sources */,
|
||||
0DCD604F19115A06006B445C /* SNTXPCNotifierInterface.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1261,6 +1318,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 +1360,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 +1390,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 +1525,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 +1559,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 +1693,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 +1732,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 +1798,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 +1826,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="14E46" 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">
|
||||
@@ -37,7 +37,7 @@
|
||||
<rect key="frame" x="18" y="65" width="444" height="60"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="CcT-ul-1eA">
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="title">Santa is an application whitelisting system for Mac OS X.
|
||||
<string key="title">Santa is an application whitelisting system for OS X.
|
||||
|
||||
There are no user-configurable settings.</string>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14E46" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
|
||||
@@ -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"/>
|
||||
@@ -34,7 +34,7 @@
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
|
||||
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -137,7 +137,7 @@
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -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>
|
||||
@@ -218,15 +218,20 @@ DQ
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.parentName" id="arL-Mc-4xj">
|
||||
<binding destination="-2" name="displayPatternValue1" keyPath="self.event.parentName" id="Lce-TO-q9V">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
<string key="NSDisplayPattern">%{value1}@ (%{value2}@)</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
<binding destination="-2" name="displayPatternValue2" keyPath="self.event.ppid" previousBinding="Lce-TO-q9V" id="ofI-kH-F2d">
|
||||
<dictionary key="options">
|
||||
<string key="NSDisplayPattern">%{value1}@ (%{value2}@)</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
@@ -234,6 +239,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 +261,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"/>
|
||||
|
||||
@@ -42,14 +42,22 @@
|
||||
self.notificationManager = [[SNTNotificationManager alloc] init];
|
||||
|
||||
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
[workspaceNotifications addObserver:self
|
||||
selector:@selector(killConnection)
|
||||
name:NSWorkspaceSessionDidResignActiveNotification
|
||||
object:nil];
|
||||
[workspaceNotifications addObserver:self
|
||||
selector:@selector(createConnection)
|
||||
name:NSWorkspaceSessionDidBecomeActiveNotification
|
||||
object:nil];
|
||||
|
||||
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidResignActiveNotification
|
||||
object:nil
|
||||
queue:[NSOperationQueue currentQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
self.listener.invalidationHandler = nil;
|
||||
self.listener.rejectedHandler = nil;
|
||||
[self.listener invalidate];
|
||||
self.listener = nil;
|
||||
}];
|
||||
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
|
||||
object:nil
|
||||
queue:[NSOperationQueue currentQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self attemptReconnection];
|
||||
}];
|
||||
|
||||
[self createConnection];
|
||||
}
|
||||
@@ -70,23 +78,16 @@
|
||||
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];
|
||||
}
|
||||
|
||||
- (void)killConnection {
|
||||
self.listener.invalidationHandler = nil;
|
||||
[self.listener invalidate];
|
||||
self.listener = nil;
|
||||
}
|
||||
|
||||
- (void)attemptReconnection {
|
||||
// TODO(rah): Make this smarter.
|
||||
sleep(10);
|
||||
[self performSelectorOnMainThread:@selector(createConnection) withObject:nil waitUntilDone:NO];
|
||||
sleep(5);
|
||||
[self createConnection];
|
||||
}
|
||||
|
||||
#pragma mark Menu Management
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -133,8 +133,11 @@
|
||||
if ([self.customMessage length] > 0) {
|
||||
message = self.customMessage;
|
||||
} else {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because its trustworthiness cannot be determined.";
|
||||
message = [[SNTConfigurator configurator] defaultBlockMessage];
|
||||
if (!message) {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because its trustworthiness cannot be determined.";
|
||||
}
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,9 +31,27 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
@property(nonatomic) santa_clientmode_t clientMode;
|
||||
|
||||
///
|
||||
/// Whether or not to log all events, even for whitelisted binaries.
|
||||
/// If set to NO, file writes won't be logged. Defaults to YES.
|
||||
///
|
||||
@property(nonatomic) BOOL logAllEvents;
|
||||
@property(nonatomic) BOOL logFileChanges;
|
||||
|
||||
///
|
||||
/// The regex of whitelisted paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
|
||||
|
||||
///
|
||||
/// The regex of blacklisted paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *blacklistPathRegex;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
@@ -64,6 +82,12 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventDetailText;
|
||||
|
||||
///
|
||||
/// For any rule that doesn't have a custom message, this setting overrides the message
|
||||
/// text that is display. If unset, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *defaultBlockMessage;
|
||||
|
||||
#pragma mark - Sync Settings
|
||||
|
||||
///
|
||||
@@ -71,6 +95,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,13 @@
|
||||
@interface SNTConfigurator ()
|
||||
@property NSString *configFilePath;
|
||||
@property NSMutableDictionary *configData;
|
||||
|
||||
/// Creating NSRegularExpression objects is not fast, so cache them.
|
||||
@property NSRegularExpression *cachedWhitelistDirRegex;
|
||||
@property NSRegularExpression *cachedBlacklistDirRegex;
|
||||
|
||||
/// 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,12 +36,14 @@ NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
|
||||
/// The keys in the config file
|
||||
static NSString * const kClientModeKey = @"ClientMode";
|
||||
|
||||
static NSString * const kLogAllEventsKey = @"LogAllEvents";
|
||||
static NSString * const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString * const kBlacklistRegexKey = @"BlacklistRegex";
|
||||
static NSString * const kLogFileChangesKey = @"LogFileChanges";
|
||||
|
||||
static NSString * const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString * const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString * const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString * const kDefaultBlockMessage = @"DefaultBlockMessage";
|
||||
|
||||
static NSString * const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString * const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
@@ -73,6 +82,12 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return sharedConfigurator;
|
||||
}
|
||||
|
||||
#pragma mark Protected Keys
|
||||
|
||||
- (NSArray *)protectedKeys {
|
||||
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey, kLogFileChangesKey ];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (santa_clientmode_t)clientMode {
|
||||
@@ -92,12 +107,54 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)logAllEvents {
|
||||
return [self.configData[kLogAllEventsKey] boolValue];
|
||||
- (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:NULL];
|
||||
}
|
||||
return self.cachedWhitelistDirRegex;
|
||||
}
|
||||
|
||||
- (void)setLogAllEvents:(BOOL)logAllEvents {
|
||||
self.configData[kLogAllEventsKey] = @(logAllEvents);
|
||||
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kWhitelistRegexKey];
|
||||
} else {
|
||||
self.configData[kWhitelistRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedWhitelistDirRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)blacklistPathRegex {
|
||||
if (!self.cachedBlacklistDirRegex && self.configData[kBlacklistRegexKey]) {
|
||||
NSString *re = self.configData[kBlacklistRegexKey];
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedBlacklistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:NULL];
|
||||
}
|
||||
return self.cachedBlacklistDirRegex;
|
||||
}
|
||||
|
||||
- (void)setBlacklistPathRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kBlacklistRegexKey];
|
||||
} else {
|
||||
self.configData[kBlacklistRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedBlacklistDirRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (BOOL)logFileChanges {
|
||||
return [self.configData[kLogFileChangesKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setLogFileChanges:(BOOL)logFileChanges {
|
||||
self.configData[kLogFileChangesKey] = @(logFileChanges);
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
@@ -113,6 +170,10 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return self.configData[kEventDetailTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)defaultBlockMessage {
|
||||
return self.configData[kDefaultBlockMessage];
|
||||
}
|
||||
|
||||
- (NSURL *)syncBaseURL {
|
||||
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
|
||||
}
|
||||
@@ -180,8 +241,6 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
}
|
||||
|
||||
- (void)reloadConfigData {
|
||||
if (!self.configData) self.configData = [NSMutableDictionary dictionary];
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:self.configFilePath]) return;
|
||||
|
||||
@@ -204,16 +263,28 @@ 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 {
|
||||
if (!self.configData) {
|
||||
self.configData = [configData mutableCopy];
|
||||
} else {
|
||||
// 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 (geteuid() == 0 &&
|
||||
((self.configData[key] && !configData[key]) ||
|
||||
(!self.configData[key] && configData[key]) ||
|
||||
(self.configData[key] && ![self.configData[key] isEqual:configData[key]]))) {
|
||||
if (self.configData[key]) {
|
||||
configDataMutable[key] = self.configData[key];
|
||||
} else {
|
||||
[configDataMutable removeObjectForKey:key];
|
||||
}
|
||||
changed = YES;
|
||||
LOGI(@"Ignoring changed configuration key: %@", key);
|
||||
}
|
||||
}
|
||||
self.configData = configDataMutable;
|
||||
if (changed) [self saveConfigToDisk];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -82,6 +92,11 @@
|
||||
///
|
||||
- (BOOL)isScript;
|
||||
|
||||
///
|
||||
/// @return YES if this file has a bad/missing __PAGEZERO .
|
||||
///
|
||||
- (BOOL)isMissingPageZero;
|
||||
|
||||
///
|
||||
/// @return An NSBundle if this file is part of a bundle.
|
||||
///
|
||||
|
||||
@@ -20,55 +20,70 @@
|
||||
#include <mach-o/swap.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
// Simple class to hold the data of a mach_header and the offset within the file
|
||||
// in which that header was found.
|
||||
@interface MachHeaderWithOffset : NSObject
|
||||
@property NSData *data;
|
||||
@property uint32_t offset;
|
||||
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset;
|
||||
@end
|
||||
@implementation MachHeaderWithOffset
|
||||
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_data = data;
|
||||
_offset = offset;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SNTFileInfo ()
|
||||
@property NSString *path;
|
||||
@property NSData *fileData;
|
||||
|
||||
// Dictionary of MachHeaderWithOffset objects where the keys are the architecture strings
|
||||
@property NSDictionary *machHeaders;
|
||||
|
||||
// Cached properties
|
||||
@property NSBundle *bundleRef;
|
||||
@property NSDictionary *infoDict;
|
||||
@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:NSDataReadingUncached
|
||||
error:error];
|
||||
if (_fileData.length == 0) return nil;
|
||||
[self parseMachHeaders];
|
||||
}
|
||||
|
||||
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 +95,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,88 +116,70 @@
|
||||
}
|
||||
|
||||
- (NSArray *)architectures {
|
||||
if (![self isMachO]) return nil;
|
||||
|
||||
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];
|
||||
|
||||
// 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];
|
||||
|
||||
// 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]];
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
struct mach_header *hdr = [self firstMachHeader];
|
||||
return @[ [self nameForCPUType:hdr->cputype] ];
|
||||
}
|
||||
return nil;
|
||||
return [self.machHeaders allKeys];
|
||||
}
|
||||
|
||||
- (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) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (mach_header && mach_header->filetype == MH_DYLIB) return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isKext {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_KEXT_BUNDLE) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
return ([self.fileData length] >= 160 &&
|
||||
([self isMachHeader:(struct mach_header *)[self.fileData bytes]] || [self isFat]));
|
||||
return ([self.machHeaders count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)isFat {
|
||||
return ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]);
|
||||
return ([self.machHeaders count] > 1);
|
||||
}
|
||||
|
||||
- (BOOL)isScript {
|
||||
if ([self.fileData length] < 1) return NO;
|
||||
|
||||
char magic[2];
|
||||
[self.fileData getBytes:&magic length:2];
|
||||
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 2)] bytes];
|
||||
return (strncmp("#!", magic, 2) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_OBJECT ||
|
||||
mach_header->filetype == MH_EXECUTE ||
|
||||
mach_header->filetype == MH_PRELOAD) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (mach_header->filetype == MH_OBJECT || mach_header->filetype == MH_EXECUTE) return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isMissingPageZero {
|
||||
// This method only checks i386 arch because the kernel enforces this for other archs
|
||||
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
|
||||
MachHeaderWithOffset *x86Header = self.machHeaders[@"i386"];
|
||||
if (!x86Header) return NO;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
|
||||
if (mh->filetype != MH_EXECUTE) return NO;
|
||||
|
||||
NSRange range = NSMakeRange(x86Header.offset + sizeof(struct mach_header),
|
||||
sizeof(struct segment_command));
|
||||
NSData *lcData = [self safeSubdataWithRange:range];
|
||||
if (!lcData) return NO;
|
||||
|
||||
// This code assumes the __PAGEZERO is always the first load-command in the file.
|
||||
// Given that the OS X ABI says "the static linker creates a __PAGEZERO segment
|
||||
// as the first segment of an executable file." this should be OK.
|
||||
struct load_command *lc = (struct load_command *)[lcData bytes];
|
||||
if (lc->cmd == LC_SEGMENT) {
|
||||
struct segment_command *segment = (struct segment_command *)lc;
|
||||
if (segment->vmaddr == 0 && segment->vmsize != 0 &&
|
||||
segment->initprot == 0 && segment->maxprot == 0 &&
|
||||
strcmp("__PAGEZERO", segment->segname) == 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark Bundle Information
|
||||
|
||||
///
|
||||
@@ -206,21 +203,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 +223,84 @@
|
||||
}
|
||||
|
||||
- (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;
|
||||
if (self.bundle) {
|
||||
NSDictionary *d = self.bundle.infoDictionary;
|
||||
if (d) self.infoDict = d;
|
||||
}
|
||||
|
||||
// Look for an embedded Info.plist if there is one.
|
||||
// This could (and used to) use CFBundleCopyInfoDictionaryForURL but that uses mmap to read
|
||||
// the file and so can cause SIGBUS if the file is deleted/truncated while it's working.
|
||||
MachHeaderWithOffset *mhwo = [[self.machHeaders allValues] firstObject];
|
||||
if (!mhwo) return nil;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)mhwo.data.bytes;
|
||||
if (mh->filetype != MH_EXECUTE) return nil;
|
||||
BOOL is64 = (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64);
|
||||
uint32_t ncmds = mh->ncmds;
|
||||
uint32_t nsects = 0;
|
||||
uint64_t offset = mhwo.offset;
|
||||
|
||||
uint32_t sz_header = is64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header);
|
||||
uint32_t sz_segment = is64 ? sizeof(struct segment_command_64) : sizeof(struct segment_command);
|
||||
uint32_t sz_section = is64 ? sizeof(struct section_64) : sizeof(struct section);
|
||||
|
||||
offset += sz_header;
|
||||
|
||||
// Loop through the load commands looking for the segment named __TEXT
|
||||
for (uint32_t i = 0; i < ncmds; i++) {
|
||||
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
|
||||
if (!cmdData) return nil;
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
|
||||
if (strncmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
|
||||
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
|
||||
for (uint32_t i = 0; i < nsects; i++) {
|
||||
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
|
||||
if (!sectData) return nil;
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (strncmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
|
||||
if (!plistData) return nil;
|
||||
NSDictionary *plist;
|
||||
plist = [NSPropertyListSerialization propertyListWithData:plistData
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:NULL];
|
||||
if (plist) self.infoDict = plist;
|
||||
break;
|
||||
}
|
||||
offset += sz_section;
|
||||
}
|
||||
}
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
|
||||
self.infoDict =
|
||||
(__bridge_transfer NSDictionary *)CFBundleCopyInfoDictionaryForURL((__bridge CFURLRef)url);
|
||||
return self.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 {
|
||||
@@ -284,45 +330,77 @@
|
||||
|
||||
#pragma mark Internal Methods
|
||||
|
||||
- (void)parseMachHeaders {
|
||||
if (self.machHeaders) return;
|
||||
|
||||
// Sanity check file length
|
||||
if (self.fileData.length < sizeof(struct mach_header)) {
|
||||
self.machHeaders = [NSDictionary dictionary];
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
|
||||
|
||||
NSData *machHeader = [self parseSingleMachHeader:self.fileData];
|
||||
if (machHeader) {
|
||||
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
|
||||
machHeaders[[self nameForCPUType:mh->cputype]] = mhwo;
|
||||
} else {
|
||||
NSRange range = NSMakeRange(0, sizeof(struct fat_header));
|
||||
NSData *fatHeader = [self safeSubdataWithRange:range];
|
||||
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
|
||||
|
||||
if (fatHeader && (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM)) {
|
||||
int nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
|
||||
range = NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch) * nfat_arch);
|
||||
NSMutableData *fatArchs = [[self safeSubdataWithRange:range] mutableCopy];
|
||||
if (fatArchs) {
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[fatArchs mutableBytes];
|
||||
for (int i = 0; i < nfat_arch; i++) {
|
||||
int offset = OSSwapBigToHostInt32(fat_arch[i].offset);
|
||||
int size = OSSwapBigToHostInt32(fat_arch[i].size);
|
||||
int cputype = OSSwapBigToHostInt(fat_arch[i].cputype);
|
||||
|
||||
range = NSMakeRange(offset, size);
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:range]];
|
||||
if (machHeader) {
|
||||
NSString *key = [self nameForCPUType:cputype];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader
|
||||
offset:offset];
|
||||
machHeaders[key] = mhwo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.machHeaders = [machHeaders copy];
|
||||
}
|
||||
|
||||
- (NSData *)parseSingleMachHeader:(NSData *)inputData {
|
||||
if (inputData.length < sizeof(struct mach_header)) return nil;
|
||||
struct mach_header *mh = (struct mach_header *)[inputData bytes];
|
||||
|
||||
if (mh->magic == MH_CIGAM || mh->magic == MH_CIGAM_64) {
|
||||
NSMutableData *mutableInput = [inputData mutableCopy];
|
||||
mh = (struct mach_header *)[mutableInput mutableBytes];
|
||||
swap_mach_header(mh, NXHostByteOrder());
|
||||
}
|
||||
|
||||
if (mh->magic == MH_MAGIC || mh->magic == MH_MAGIC_64) {
|
||||
return [NSData dataWithBytes:mh length:sizeof(struct mach_header)];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
///
|
||||
/// Look through the file for the first mach_header. If the file is thin, this will be the
|
||||
/// header at the beginning of the file. If the file is fat, it will be the first
|
||||
/// architecture-specific header.
|
||||
/// Return one of the mach_header's in this file.
|
||||
///
|
||||
- (struct mach_header *)firstMachHeader {
|
||||
if (![self isMachO]) return NULL;
|
||||
return (struct mach_header *)([[[[self.machHeaders allValues] firstObject] data] bytes]);
|
||||
|
||||
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: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) return nil;
|
||||
mach_header = (struct mach_header *)[machHdr bytes];
|
||||
}
|
||||
|
||||
if ([self isMachHeader:mach_header]) {
|
||||
return mach_header;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
- (BOOL)isMachHeader:(struct mach_header *)header {
|
||||
return (header->magic == MH_MAGIC || header->magic == MH_MAGIC_64 ||
|
||||
header->magic == MH_CIGAM || header->magic == MH_CIGAM_64);
|
||||
}
|
||||
|
||||
- (BOOL)isFatHeader:(struct fat_header *)header {
|
||||
return (header->magic == FAT_MAGIC || header->magic == FAT_CIGAM);
|
||||
}
|
||||
|
||||
///
|
||||
@@ -338,6 +416,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Return a human-readable string for a cpu_type_t.
|
||||
///
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
|
||||
switch (cpuType) {
|
||||
case CPU_TYPE_X86:
|
||||
@@ -354,4 +435,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,12 @@ typedef enum {
|
||||
ACTION_RESPOND_CHECKBW_DENY = 12,
|
||||
|
||||
// NOTIFY
|
||||
ACTION_NOTIFY_EXEC_ALLOW_NODAEMON = 30,
|
||||
ACTION_NOTIFY_EXEC_ALLOW_CACHED = 31,
|
||||
ACTION_NOTIFY_EXEC_DENY_CACHED = 32,
|
||||
ACTION_NOTIFY_EXEC = 20,
|
||||
ACTION_NOTIFY_WRITE = 21,
|
||||
ACTION_NOTIFY_RENAME = 22,
|
||||
ACTION_NOTIFY_LINK = 23,
|
||||
ACTION_NOTIFY_EXCHANGE = 24,
|
||||
ACTION_NOTIFY_DELETE = 25,
|
||||
|
||||
// SHUTDOWN
|
||||
ACTION_REQUEST_SHUTDOWN = 90,
|
||||
@@ -70,10 +73,12 @@ typedef enum {
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
uint64_t vnode_id;
|
||||
uid_t userId;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
} santa_message_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
@@ -32,26 +32,29 @@
|
||||
|
||||
#else // KERNEL
|
||||
|
||||
#define LOG_LEVEL_ERROR 1
|
||||
#define LOG_LEVEL_WARN 2
|
||||
#define LOG_LEVEL_INFO 3
|
||||
#define LOG_LEVEL_DEBUG 4
|
||||
typedef enum : NSUInteger {
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARN,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG
|
||||
} LogLevel;
|
||||
|
||||
///
|
||||
/// 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(LogLevel 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,28 +14,32 @@
|
||||
|
||||
#import "SNTLogging.h"
|
||||
|
||||
#import <asl.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static int logLevel = LOG_LEVEL_DEBUG;
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static int logLevel = LOG_LEVEL_INFO; // default to info
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
static NSDateFormatter *dateFormatter;
|
||||
static NSString *binaryName;
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static const char *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];
|
||||
binaryName = [[[NSProcessInfo processInfo] processName] UTF8String];
|
||||
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
strcmp(binaryName, "santad") == 0) {
|
||||
useSyslog = YES;
|
||||
}
|
||||
});
|
||||
|
||||
if (logLevel < level) return;
|
||||
@@ -45,19 +49,22 @@ void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
NSString *s = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
va_end(args);
|
||||
|
||||
// Only prepend timestamp, severity and binary name if stdout is not a TTY
|
||||
if (isatty(fileno(destination))) {
|
||||
fprintf(destination, "%s\n", [s UTF8String]);
|
||||
} else {
|
||||
NSString *levelName;
|
||||
if (useSyslog) {
|
||||
aslclient client = asl_open(NULL, "com.google.santa", 0);
|
||||
asl_set_filter(client, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
|
||||
|
||||
char *levelName;
|
||||
int syslogLevel = ASL_LEVEL_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 = ASL_LEVEL_ERR; break;
|
||||
case LOG_LEVEL_WARN: levelName = "W"; syslogLevel = ASL_LEVEL_WARNING; break;
|
||||
case LOG_LEVEL_INFO: levelName = "I"; syslogLevel = ASL_LEVEL_INFO; break;
|
||||
case LOG_LEVEL_DEBUG: levelName = "D"; syslogLevel = ASL_LEVEL_DEBUG; break;
|
||||
}
|
||||
|
||||
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
|
||||
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
|
||||
asl_close(client);
|
||||
} else {
|
||||
fprintf(destination, "%s\n", [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.
|
||||
|
||||
@@ -60,4 +60,25 @@
|
||||
#undef DECODE
|
||||
#undef ENCODE
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (other == self) return YES;
|
||||
if (![other isKindOfClass:[SNTRule class]]) return NO;
|
||||
SNTRule *o = other;
|
||||
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
NSUInteger prime = 31;
|
||||
NSUInteger result = 1;
|
||||
result = prime * result + [self.shasum hash];
|
||||
result = prime * result + self.state;
|
||||
result = prime * result + self.type;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %d, Type: %d",
|
||||
self.shasum, self.state, self.type];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (other == self) return YES;
|
||||
if (![other isKindOfClass:[SNTStoredEvent class]]) return NO;
|
||||
SNTStoredEvent *o;
|
||||
SNTStoredEvent *o = other;
|
||||
return ([self.fileSHA256 isEqual:o.fileSHA256] && [self.idx isEqual:o.idx]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -22,51 +22,54 @@ OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
|
||||
bool SantaDecisionManager::init() {
|
||||
if (!super::init()) return false;
|
||||
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", lck_grp_attr_alloc_init());
|
||||
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_,
|
||||
lck_attr_alloc_init());
|
||||
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
|
||||
|
||||
sdm_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
|
||||
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_attr_) {
|
||||
lck_attr_free(sdm_lock_attr_);
|
||||
sdm_lock_attr_ = NULL;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_) {
|
||||
lck_grp_free(sdm_lock_grp_);
|
||||
sdm_lock_grp_ = NULL;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_attr_) {
|
||||
lck_grp_attr_free(sdm_lock_grp_attr_);
|
||||
sdm_lock_grp_attr_ = NULL;
|
||||
}
|
||||
|
||||
super::free();
|
||||
}
|
||||
|
||||
@@ -79,36 +82,47 @@ 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;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DisconnectClient(bool itDied) {
|
||||
if (client_pid_ < 1) return;
|
||||
|
||||
client_pid_ = -1;
|
||||
client_pid_ = 0;
|
||||
|
||||
// Ask santad to shutdown, in case it's running.
|
||||
if (!itDied) {
|
||||
santa_message_t message = {.action = ACTION_REQUEST_SHUTDOWN};
|
||||
santa_message_t *message = new santa_message_t;
|
||||
message->action = ACTION_REQUEST_SHUTDOWN;
|
||||
PostToQueue(message);
|
||||
delete 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() {
|
||||
return client_pid_ > 0;
|
||||
proc_t p = proc_find(client_pid_);
|
||||
bool is_exiting = proc_exiting(p);
|
||||
proc_rele(p);
|
||||
return (client_pid_ > 0 && !is_exiting);
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetMemoryDescriptor() {
|
||||
return shared_memory_;
|
||||
return dataqueue_->getMemoryDescriptor();
|
||||
}
|
||||
|
||||
#pragma mark Listener Control
|
||||
@@ -153,32 +167,32 @@ kern_return_t SantaDecisionManager::StopListener() {
|
||||
|
||||
void SantaDecisionManager::AddToCache(
|
||||
const char *identifier, santa_action_t decision, uint64_t microsecs) {
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
|
||||
if (cached_decisions_->getCount() > kMaxCacheSize) {
|
||||
// This could be made a _lot_ smarter, say only removing entries older
|
||||
// than a certain time period. However, with a kMaxCacheSize set
|
||||
// sufficiently large and a kMaxAllowCacheTimeMilliseconds set
|
||||
// sufficiently low, this should only ever occur if someone is purposefully
|
||||
// trying to make the cache grow.
|
||||
LOGD("Cache too large, flushing.");
|
||||
cached_decisions_->flushCollection();
|
||||
LOGI("Cache too large, flushing.");
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
if (decision == ACTION_REQUEST_CHECKBW) {
|
||||
SantaMessage *pending = new SantaMessage();
|
||||
pending->setAction(ACTION_REQUEST_CHECKBW, 0);
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
cached_decisions_->setObject(identifier, pending);
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
pending->release(); // it was retained when added to the dictionary
|
||||
} else {
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
SantaMessage *pending =
|
||||
OSDynamicCast(SantaMessage, cached_decisions_->getObject(identifier));
|
||||
if (pending) {
|
||||
pending->setAction(decision, microsecs);
|
||||
}
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
}
|
||||
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::CacheCheck(const char *identifier) {
|
||||
@@ -252,11 +266,14 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
santa_message_t message, char *vnode_id_str) {
|
||||
santa_message_t *message, const char *vnode_id_str) {
|
||||
santa_action_t return_action = ACTION_UNSET;
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Add pending request to cache.
|
||||
AddToCache(vnode_id_str, ACTION_REQUEST_CHECKBW, 0);
|
||||
|
||||
// Send request to daemon...
|
||||
if (!PostToQueue(message)) {
|
||||
OSIncrementAtomic(&failed_queue_requests_);
|
||||
@@ -264,26 +281,23 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
LOGE("Failed to queue more than %d requests, killing daemon",
|
||||
kMaxQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
client_pid_ = 0;
|
||||
}
|
||||
LOGE("Failed to queue request for %s.", message.path);
|
||||
LOGE("Failed to queue request for %s.", message->path);
|
||||
CacheCheck(vnode_id_str);
|
||||
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)) {
|
||||
LOGE("Daemon process did not respond correctly. Allowing executions "
|
||||
"until it comes back.");
|
||||
"until it comes back. Executable path: %s", message->path);
|
||||
CacheCheck(vnode_id_str);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
@@ -292,65 +306,66 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
}
|
||||
|
||||
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);
|
||||
if (!ClientConnected()) return ACTION_RESPOND_CHECKBW_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
|
||||
// If item wasn in cache return it.
|
||||
// If item was in cache return it.
|
||||
if CHECKBW_RESPONSE_VALID(return_action) return return_action;
|
||||
|
||||
// Add pending request to cache.
|
||||
AddToCache(vnode_id_str, ACTION_REQUEST_CHECKBW, 0);
|
||||
|
||||
// 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.pid = proc_selfpid();
|
||||
message.ppid = proc_selfppid();
|
||||
message.action = ACTION_REQUEST_CHECKBW;
|
||||
message.vnode_id = vnode_id;
|
||||
|
||||
if (ClientConnected()) {
|
||||
return GetFromDaemon(message, vnode_id_str);
|
||||
} else {
|
||||
LOGI("Execution request without daemon running: %s", path);
|
||||
message.action = ACTION_NOTIFY_EXEC_ALLOW_NODAEMON;
|
||||
PostToQueue(message);
|
||||
return ACTION_RESPOND_CHECKBW_ALLOW;
|
||||
}
|
||||
santa_message_t *message = NewMessage();
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
message->action = ACTION_REQUEST_CHECKBW;
|
||||
message->vnode_id = vnode_id;
|
||||
santa_action_t ret = GetFromDaemon(message, vnode_id_str);
|
||||
delete message;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
bool SantaDecisionManager::PostToQueue(santa_message_t message) {
|
||||
santa_message_t* SantaDecisionManager::NewMessage() {
|
||||
santa_message_t *message = new santa_message_t;
|
||||
message->uid = kauth_getuid();
|
||||
message->gid = kauth_getgid();
|
||||
message->pid = proc_selfpid();
|
||||
message->ppid = proc_selfppid();
|
||||
return message;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PostToQueue(santa_message_t *message) {
|
||||
bool kr = false;
|
||||
kr = dataqueue_->enqueue(&message, sizeof(message));
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
kr = dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
// If enqueue failed, pop an item off the queue and try again.
|
||||
uint32_t dataSize = sizeof(santa_message_t);
|
||||
dataqueue_->dequeue(0, &dataSize);
|
||||
kr = dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -371,6 +386,95 @@ 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.).
|
||||
if (vnode_vtype(vp) != 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;
|
||||
}
|
||||
}
|
||||
|
||||
void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (vp) {
|
||||
vfs_context_t context = vfs_context_create(NULL);
|
||||
uint64_t vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
vfs_context_rele(context);
|
||||
|
||||
if (action == KAUTH_FILEOP_CLOSE) {
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
CacheCheck(vnode_id_str);
|
||||
} else if (action == KAUTH_FILEOP_EXEC) {
|
||||
santa_message_t *message = NewMessage();
|
||||
message->vnode_id = vnode_id;
|
||||
message->action = ACTION_NOTIFY_EXEC;
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
PostToQueue(message);
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out modifications to locations that are not that useful.
|
||||
if (ClientConnected() &&
|
||||
!strprefix(path, "/private/tmp") &&
|
||||
!strprefix(path, "/private/var/folders") &&
|
||||
!strprefix(path, "/.") &&
|
||||
!strprefix(path, "/dev")) {
|
||||
santa_message_t *message = NewMessage();
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_CLOSE:
|
||||
message->action = ACTION_NOTIFY_WRITE; break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
message->action = ACTION_NOTIFY_RENAME; break;
|
||||
case KAUTH_FILEOP_LINK:
|
||||
message->action = ACTION_NOTIFY_LINK; break;
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
message->action = ACTION_NOTIFY_EXCHANGE; break;
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
message->action = ACTION_NOTIFY_DELETE; break;
|
||||
default: delete message; return;
|
||||
}
|
||||
|
||||
PostToQueue(message);
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
#pragma mark Kauth Callbacks
|
||||
@@ -378,26 +482,35 @@ 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.");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
SantaDecisionManager *sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
vnode_t vp = NULL;
|
||||
char *path = NULL;
|
||||
char *new_path = NULL;
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_CLOSE:
|
||||
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED)) return KAUTH_RESULT_DEFER;
|
||||
// Intentional fall-through
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
case KAUTH_FILEOP_EXEC:
|
||||
vp = reinterpret_cast<vnode_t>(arg0);
|
||||
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
case KAUTH_FILEOP_LINK:
|
||||
path = reinterpret_cast<char *>(arg0);
|
||||
new_path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
default:
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
sdm->IncrementListenerInvocations();
|
||||
|
||||
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(action, vp, path, new_path);
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
@@ -406,64 +519,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,28 @@ 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.
|
||||
///
|
||||
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path, const char *new_path);
|
||||
|
||||
protected:
|
||||
///
|
||||
/// The maximum number of milliseconds a cached deny message should be
|
||||
@@ -131,12 +123,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 +139,77 @@ 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(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);
|
||||
|
||||
///
|
||||
/// Creates a new santa_message_t with some fields pre-filled.
|
||||
///
|
||||
santa_message_t* NewMessage();
|
||||
|
||||
/// Returns the current system uptime in microseconds
|
||||
static uint64_t GetCurrentUptime();
|
||||
|
||||
private:
|
||||
lck_grp_t *sdm_lock_grp_;
|
||||
lck_grp_attr_t *sdm_lock_grp_attr_;
|
||||
|
||||
lck_attr_t *sdm_lock_attr_;
|
||||
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_;
|
||||
|
||||
@@ -124,8 +124,10 @@ IOReturn SantaDriverClient::static_allow_binary(
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == NULL) return kIOReturnBadArgument;
|
||||
|
||||
return target->allow_binary(
|
||||
*(static_cast<const uint64_t *>(arguments->scalarInput)));
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::deny_binary(const uint64_t vnode_id) {
|
||||
@@ -141,8 +143,10 @@ IOReturn SantaDriverClient::static_deny_binary(
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == NULL) return kIOReturnBadArgument;
|
||||
|
||||
return target->deny_binary(
|
||||
*(static_cast<const uint64_t *>(arguments->scalarInput)));
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clear_cache() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ REGISTER_COMMAND_NAME(@"binaryinfo")
|
||||
printf("%-19s: %s\n", "Type", [[fileInfo machoType] UTF8String]);
|
||||
}
|
||||
|
||||
if ([fileInfo isMissingPageZero]) {
|
||||
printf("%-19s: %s\n", "Page Zero", "__PAGEZERO segment missing/bad!");
|
||||
}
|
||||
|
||||
SNTCodesignChecker *csc = [[SNTCodesignChecker alloc] initWithBinaryPath:filePath];
|
||||
|
||||
printf("%-19s: %s\n", "Code-signed", (csc) ? "Yes" : "No");
|
||||
|
||||
@@ -56,6 +56,12 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
@" --sha256 {sha256}: hash to add\n");
|
||||
}
|
||||
|
||||
+ (void)printErrorUsageAndExit:(NSString *)error {
|
||||
printf("%s\n\n", [error UTF8String]);
|
||||
printf("%s\n", [[self longHelpText] UTF8String]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
@@ -74,8 +80,7 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
|
||||
// add or remove
|
||||
if (!action) {
|
||||
printf("Missing action - add or remove?\n");
|
||||
exit(1);
|
||||
[self printErrorUsageAndExit:@"Missing action"];
|
||||
}
|
||||
|
||||
int state = RULESTATE_UNKNOWN;
|
||||
@@ -84,8 +89,7 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
} else if ([action compare:@"remove" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||
state = RULESTATE_REMOVE;
|
||||
} else {
|
||||
printf("Unknown action, expected add or remove.\n");
|
||||
exit(1);
|
||||
[self printErrorUsageAndExit:@"Unknown action"];
|
||||
}
|
||||
|
||||
NSString *customMsg = @"";
|
||||
@@ -103,46 +107,42 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
} else if ([argument compare:@"--silent-blacklist" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||
state = RULESTATE_SILENT_BLACKLIST;
|
||||
} else if ([argument compare:@"--message" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||
if (++i > ([arguments count])) {
|
||||
printf("No message specified.\n");
|
||||
if (++i > arguments.count - 1) {
|
||||
[self printErrorUsageAndExit:@"No message specified"];
|
||||
}
|
||||
|
||||
customMsg = [arguments objectAtIndex:i];
|
||||
} else if ([argument compare:@"--path" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||
if (++i > ([arguments count])) {
|
||||
printf("No path specified.\n");
|
||||
if (++i > arguments.count - 1) {
|
||||
[self printErrorUsageAndExit:@"No path specified"];
|
||||
}
|
||||
|
||||
filePath = [arguments objectAtIndex:i];
|
||||
} else if ([argument compare:@"--sha256" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||
if (++i > ([arguments count])) {
|
||||
printf("No SHA-256 specified.\n");
|
||||
if (++i > arguments.count - 1) {
|
||||
[self printErrorUsageAndExit:@"No SHA-256 specified"];
|
||||
}
|
||||
|
||||
SHA256 = [arguments objectAtIndex:i];
|
||||
} else {
|
||||
printf("Unknown argument %s.\n", [argument UTF8String]);
|
||||
exit(1);
|
||||
[self printErrorUsageAndExit:[@"Unknown argument: %@" stringByAppendingString:argument]];
|
||||
}
|
||||
}
|
||||
|
||||
if (state == RULESTATE_UNKNOWN) {
|
||||
printf("No state specified.\n");
|
||||
exit(1);
|
||||
[self printErrorUsageAndExit:@"No state specified"];
|
||||
}
|
||||
|
||||
if (filePath) {
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
|
||||
if (!fileInfo) {
|
||||
printf("Not a regular file or executable bundle.\n");
|
||||
exit(1);
|
||||
[self printErrorUsageAndExit:@"Provided path is not a regular file or executable bundle"];
|
||||
}
|
||||
|
||||
SHA256 = [fileInfo SHA256];
|
||||
} else if (SHA256) {
|
||||
} else {
|
||||
printf("No SHA-256 or binary specified.\n");
|
||||
exit(1);
|
||||
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
|
||||
}
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
[config setTLSMinimumSupportedProtocol:kTLSProtocol12];
|
||||
[config setHTTPShouldUsePipelining:YES];
|
||||
return [self initWithSessionConfiguration:config];
|
||||
@@ -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";
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
#import "SNTCertificate.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
@@ -97,6 +98,13 @@
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
NSData *compressed = [requestBody zlibCompressed];
|
||||
if (compressed) {
|
||||
requestBody = compressed;
|
||||
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
|
||||
}
|
||||
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
@@ -104,12 +112,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];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "SNTKernelCommon.h"
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTSystemInfo.h"
|
||||
@@ -47,17 +48,25 @@
|
||||
NSData *requestBody = [NSJSONSerialization dataWithJSONObject:requestDict
|
||||
options:0
|
||||
error:nil];
|
||||
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
NSData *compressed = [requestBody zlibCompressed];
|
||||
if (compressed) {
|
||||
requestBody = compressed;
|
||||
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
|
||||
}
|
||||
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
[[session dataTaskWithRequest:req 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);
|
||||
@@ -73,9 +82,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 {
|
||||
@@ -107,6 +107,7 @@
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.shasum = dict[kRuleSHA256];
|
||||
if (newRule.shasum.length != 64) return nil;
|
||||
|
||||
NSString *policyString = dict[kRulePolicy];
|
||||
if ([policyString isEqual:kRulePolicyWhitelist]) {
|
||||
@@ -131,7 +132,7 @@
|
||||
}
|
||||
|
||||
NSString *customMsg = dict[kRuleCustomMsg];
|
||||
if (customMsg) {
|
||||
if (customMsg.length) {
|
||||
newRule.customMsg = customMsg;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -25,6 +24,7 @@
|
||||
#import "SNTDaemonControlController.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTEventLog.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTExecutionController.h"
|
||||
#import "SNTFileWatcher.h"
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
@interface SNTApplication ()
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTEventLog *eventLog;
|
||||
@property SNTEventTable *eventTable;
|
||||
@property SNTExecutionController *execController;
|
||||
@property SNTFileWatcher *configFileWatcher;
|
||||
@@ -94,11 +95,14 @@
|
||||
chmod([kDefaultConfigFilePath fileSystemRepresentation], 0644);
|
||||
}];
|
||||
|
||||
_eventLog = [[SNTEventLog alloc] init];
|
||||
|
||||
// Initialize the binary checker object
|
||||
_execController = [[SNTExecutionController alloc] initWithDriverManager:_driverManager
|
||||
ruleTable:_ruleTable
|
||||
eventTable:_eventTable
|
||||
notifierConnection:_notifierConnection];
|
||||
notifierConnection:_notifierConnection
|
||||
eventLog:_eventLog];
|
||||
if (!_execController) return nil;
|
||||
}
|
||||
|
||||
@@ -120,27 +124,27 @@
|
||||
LOGI(@"Driver requested a shutdown");
|
||||
exit(0);
|
||||
}
|
||||
case ACTION_NOTIFY_EXEC_ALLOW_NODAEMON:
|
||||
case ACTION_NOTIFY_EXEC_ALLOW_CACHED:
|
||||
case ACTION_NOTIFY_EXEC_DENY_CACHED: {
|
||||
// TODO(rah): Implement.
|
||||
case ACTION_NOTIFY_DELETE:
|
||||
case ACTION_NOTIFY_EXCHANGE:
|
||||
case ACTION_NOTIFY_LINK:
|
||||
case ACTION_NOTIFY_RENAME:
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
dispatch_async(q, ^{
|
||||
if ([[SNTConfigurator configurator] logFileChanges]) {
|
||||
[self.eventLog logFileModification:message];
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXEC: {
|
||||
dispatch_async(q, ^{
|
||||
[self.eventLog logAllowedExecution:message];
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ACTION_REQUEST_CHECKBW: {
|
||||
// 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;
|
||||
}
|
||||
|
||||
32
Source/santad/SNTCachedDecision.h
Normal file
32
Source/santad/SNTCachedDecision.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Store information about executions from decision making for later logging.
|
||||
///
|
||||
@interface SNTCachedDecision : NSObject
|
||||
|
||||
@property uint64_t vnodeId;
|
||||
@property santa_eventstate_t decision;
|
||||
@property NSString *decisionExtra;
|
||||
@property NSString *sha256;
|
||||
@property NSString *certSHA256;
|
||||
@property NSString *certCommonName;
|
||||
|
||||
@property NSString *customMsg;
|
||||
@property BOOL silentBlock;
|
||||
|
||||
@end
|
||||
18
Source/santad/SNTCachedDecision.m
Normal file
18
Source/santad/SNTCachedDecision.m
Normal file
@@ -0,0 +1,18 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
|
||||
@implementation SNTCachedDecision
|
||||
@end
|
||||
@@ -17,21 +17,61 @@
|
||||
#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:30];
|
||||
}
|
||||
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];
|
||||
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(EPERM);
|
||||
}
|
||||
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog", 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 +134,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
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
|
||||
#import "SNTDatabaseController.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTRuleTable.h"
|
||||
@@ -31,8 +34,10 @@ static NSString * const kEventsDatabaseName = @"events.db";
|
||||
[self createDatabasePath];
|
||||
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kEventsDatabaseName];
|
||||
eventDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
chown([fullPath UTF8String], 0, 0);
|
||||
chmod([fullPath UTF8String], 0600);
|
||||
|
||||
#ifndef DEBUG
|
||||
#ifndef DEBUG
|
||||
[eventDatabaseQueue inDatabase:^(FMDatabase *db) { db.logsErrors = NO; }];
|
||||
#endif
|
||||
});
|
||||
@@ -47,8 +52,10 @@ static NSString * const kEventsDatabaseName = @"events.db";
|
||||
[self createDatabasePath];
|
||||
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kRulesDatabaseName];
|
||||
ruleDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
chown([fullPath UTF8String], 0, 0);
|
||||
chmod([fullPath UTF8String], 0600);
|
||||
|
||||
#ifndef DEBUG
|
||||
#ifndef DEBUG
|
||||
[ruleDatabaseQueue inDatabase:^(FMDatabase *db) { db.logsErrors = NO; }];
|
||||
#endif
|
||||
});
|
||||
|
||||
@@ -87,8 +87,6 @@ static const int MAX_DELAY = 15;
|
||||
|
||||
- (void)listenWithBlock:(void (^)(santa_message_t message))callback {
|
||||
kern_return_t kr;
|
||||
santa_message_t vdata;
|
||||
UInt32 dataSize;
|
||||
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
@@ -120,10 +118,11 @@ static const int MAX_DELAY = 15;
|
||||
}
|
||||
|
||||
self.queueMemory = (IODataQueueMemory *)address;
|
||||
dataSize = sizeof(vdata);
|
||||
|
||||
do {
|
||||
while (IODataQueueDataAvailable(self.queueMemory)) {
|
||||
santa_message_t vdata;
|
||||
uint32_t dataSize = sizeof(vdata);
|
||||
kr = IODataQueueDequeue(self.queueMemory, &vdata, &dataSize);
|
||||
if (kr == kIOReturnSuccess) {
|
||||
callback(vdata);
|
||||
|
||||
30
Source/santad/SNTEventLog.h
Normal file
30
Source/santad/SNTEventLog.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
|
||||
#import "SNTKernelCommon.h"
|
||||
@class SNTCachedDecision;
|
||||
|
||||
///
|
||||
/// Logs execution and file write events to syslog
|
||||
///
|
||||
@interface SNTEventLog : NSObject
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message;
|
||||
|
||||
- (void)saveDecisionDetails:(SNTCachedDecision *)cd;
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message;
|
||||
- (void)logAllowedExecution:(santa_message_t)message;
|
||||
|
||||
@end
|
||||
216
Source/santad/SNTEventLog.m
Normal file
216
Source/santad/SNTEventLog.m
Normal file
@@ -0,0 +1,216 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTEventLog.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCertificate.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
#import "SNTLogging.h"
|
||||
|
||||
@interface SNTEventLog ()
|
||||
@property NSMutableDictionary *detailStore;
|
||||
@end
|
||||
|
||||
@implementation SNTEventLog
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_detailStore = [NSMutableDictionary dictionaryWithCapacity:10000];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)saveDecisionDetails:(SNTCachedDecision *)cd {
|
||||
self.detailStore[@(cd.vnodeId)] = cd;
|
||||
}
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message {
|
||||
NSString *action, *path, *newpath, *sha256, *outStr;
|
||||
|
||||
path = @(message.path);
|
||||
|
||||
switch (message.action) {
|
||||
case ACTION_NOTIFY_DELETE: {
|
||||
action = @"DELETE";
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXCHANGE: {
|
||||
action = @"EXCHANGE";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_LINK: {
|
||||
action = @"LINK";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_RENAME: {
|
||||
action = @"RENAME";
|
||||
newpath = @(message.newpath);
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
action = @"WRITE";
|
||||
struct stat filestat;
|
||||
stat(message.path, &filestat);
|
||||
|
||||
if (filestat.st_size < 1024 * 1024) {
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sha256 = fileInfo.SHA256;
|
||||
} else {
|
||||
sha256 = @"(too large)";
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: action = @"UNKNOWN"; break;
|
||||
}
|
||||
|
||||
outStr = [NSString stringWithFormat:@"action=%@|path=%@", action, [self sanitizeString:path]];
|
||||
if (newpath) {
|
||||
outStr = [outStr stringByAppendingFormat:@"|newpath=%@", [self sanitizeString:newpath]];
|
||||
}
|
||||
outStr = [outStr stringByAppendingFormat:@"|pid=%d|ppid=%d|uid=%d|gid=%d",
|
||||
message.pid, message.ppid, message.uid, message.gid];
|
||||
if (sha256) {
|
||||
outStr = [outStr stringByAppendingFormat:@"|sha256=%@", sha256];
|
||||
}
|
||||
|
||||
LOGI(@"%@", outStr);
|
||||
}
|
||||
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message {
|
||||
[self logExecution:message withDecision:cd];
|
||||
}
|
||||
|
||||
- (void)logAllowedExecution:(santa_message_t)message {
|
||||
SNTCachedDecision *cd = self.detailStore[@(message.vnode_id)];
|
||||
[self logExecution:message withDecision:cd];
|
||||
}
|
||||
|
||||
- (void)logExecution:(santa_message_t)message withDecision:(SNTCachedDecision *)cd {
|
||||
NSString *d, *r, *args, *outLog;
|
||||
|
||||
switch (cd.decision) {
|
||||
case EVENTSTATE_ALLOW_BINARY:
|
||||
d = @"ALLOW"; r = @"BINARY"; args = [self argsForPid:message.pid]; break;
|
||||
case EVENTSTATE_ALLOW_CERTIFICATE:
|
||||
d = @"ALLOW"; r = @"CERTIFICATE"; args = [self argsForPid:message.pid]; break;
|
||||
case EVENTSTATE_ALLOW_SCOPE:
|
||||
d = @"ALLOW"; r = @"SCOPE"; args = [self argsForPid:message.pid]; break;
|
||||
case EVENTSTATE_ALLOW_UNKNOWN:
|
||||
d = @"ALLOW"; r = @"UNKNOWN"; args = [self argsForPid:message.pid]; break;
|
||||
case EVENTSTATE_BLOCK_BINARY:
|
||||
d = @"DENY"; r = @"BINARY"; break;
|
||||
case EVENTSTATE_BLOCK_CERTIFICATE:
|
||||
d = @"DENY"; r = @"CERT"; break;
|
||||
case EVENTSTATE_BLOCK_SCOPE:
|
||||
d = @"DENY"; r = @"SCOPE"; break;
|
||||
case EVENTSTATE_BLOCK_UNKNOWN:
|
||||
d = @"DENY"; r = @"UNKNOWN"; break;
|
||||
default:
|
||||
d = @"ALLOW"; r = @"NOTRUNNING"; args = [self argsForPid:message.pid]; break;
|
||||
}
|
||||
|
||||
outLog = [NSString stringWithFormat:@"action=EXEC|decision=%@|reason=%@", d, r];
|
||||
|
||||
if (cd.decisionExtra) {
|
||||
outLog = [outLog stringByAppendingFormat:@"|explain=%@", cd.decisionExtra];
|
||||
}
|
||||
|
||||
outLog = [outLog stringByAppendingFormat:@"|sha256=%@|path=%@|args=%@",
|
||||
cd.sha256, [self sanitizeString:@(message.path)], [self sanitizeString:args]];
|
||||
|
||||
if (cd.certSHA256) {
|
||||
outLog = [outLog stringByAppendingFormat:@"|cert_sha256=%@|cert_cn=%@",
|
||||
cd.certSHA256, [self sanitizeString:cd.certCommonName]];
|
||||
}
|
||||
|
||||
outLog = [outLog stringByAppendingFormat:@"|pid=%d|ppid=%d|uid=%d|gid=%d",
|
||||
message.pid, message.ppid, message.uid, message.gid];
|
||||
|
||||
LOGI(@"%@", outLog);
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
|
||||
- (NSString *)sanitizeString:(NSString *)inStr {
|
||||
inStr = [inStr stringByReplacingOccurrencesOfString:@"|" withString:@"<pipe>"];
|
||||
inStr = [inStr stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
|
||||
inStr = [inStr stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
|
||||
return inStr;
|
||||
}
|
||||
|
||||
- (NSString *)argsForPid:(pid_t)pid {
|
||||
int mib[3];
|
||||
|
||||
// Get size of buffer required to store process arguments.
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_ARGMAX;
|
||||
int argmax;
|
||||
size_t size = sizeof(argmax);
|
||||
|
||||
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) return nil;
|
||||
|
||||
// Create buffer to store args
|
||||
NSMutableData *argsdata = [NSMutableData dataWithCapacity:argmax];
|
||||
char *argsdatabytes = (char *)argsdata.mutableBytes;
|
||||
|
||||
// Fetch args
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROCARGS2;
|
||||
mib[2] = pid;
|
||||
size = (size_t)argmax;
|
||||
if (sysctl(mib, 3, argsdatabytes, &size, NULL, 0) == -1) return nil;
|
||||
|
||||
// Get argc
|
||||
int argc;
|
||||
memcpy(&argc, argsdatabytes, sizeof(argc));
|
||||
|
||||
// Get pointer to beginning of string space
|
||||
char *cp;
|
||||
cp = (char *) argsdatabytes + sizeof(argc);
|
||||
|
||||
// Skip over exec_path
|
||||
for (; cp < &argsdatabytes[size]; cp++) {
|
||||
if (*cp == '\0') {
|
||||
cp++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip trailing NULL bytes
|
||||
for (; cp < &argsdatabytes[size]; cp++) if (*cp != '\0') break;
|
||||
|
||||
// Loop over the argv array, stripping newlines in each arg and putting in a new array.
|
||||
NSMutableArray *args = [NSMutableArray arrayWithCapacity:argc];
|
||||
for (int i = 0; i < argc; i++) {
|
||||
NSString *arg = @(cp);
|
||||
[args addObject:arg];
|
||||
|
||||
// Move the pointer past this string and the terminator at the end.
|
||||
cp += strlen(cp) + 1;
|
||||
}
|
||||
|
||||
// Return the args as a space-separated list
|
||||
return [args componentsJoinedByString:@" "];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -41,7 +41,6 @@
|
||||
if (!event.fileSHA256 ||
|
||||
!event.filePath ||
|
||||
!event.occurrenceDate ||
|
||||
!event.executingUser ||
|
||||
!event.decision) return NO;
|
||||
|
||||
NSData *eventData = [NSKeyedArchiver archivedDataWithRootObject:event];
|
||||
@@ -126,6 +125,9 @@
|
||||
for (NSNumber *index in indexes) {
|
||||
[self deleteEventWithId:index];
|
||||
}
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
[db executeUpdate:@"VACUUM"];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
@class SNTCodesignChecker;
|
||||
@class SNTDriverManager;
|
||||
@class SNTEventLog;
|
||||
@class SNTEventTable;
|
||||
@class SNTRuleTable;
|
||||
@class SNTXPCConnection;
|
||||
@@ -29,35 +31,28 @@
|
||||
/// + (If denied or unknown) Storing details about the execution event to the database
|
||||
/// for upload and spwaning santactl to quickly try and send that to the server.
|
||||
/// + (If denied) Potentially sending a message to SantaGUI to notify the user
|
||||
/// + Logging the event to the log file
|
||||
///
|
||||
@interface SNTExecutionController : NSObject
|
||||
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTRuleTable *ruleTable;
|
||||
@property SNTEventLog *eventLog;
|
||||
@property SNTEventTable *eventTable;
|
||||
@property SNTRuleTable *ruleTable;
|
||||
@property SNTXPCConnection *notifierConnection;
|
||||
|
||||
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
|
||||
ruleTable:(SNTRuleTable *)ruleTable
|
||||
eventTable:(SNTEventTable *)eventTable
|
||||
notifierConnection:(SNTXPCConnection *)notifierConn;
|
||||
notifierConnection:(SNTXPCConnection *)notifierConn
|
||||
eventLog:(SNTEventLog *)eventLog;
|
||||
|
||||
///
|
||||
/// Handles the logic of deciding whether to allow the binary to run or not, sends the response to
|
||||
/// 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,15 +15,19 @@
|
||||
#import "SNTExecutionController.h"
|
||||
|
||||
#include <libproc.h>
|
||||
#include <pwd.h>
|
||||
#include <utmpx.h>
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCertificate.h"
|
||||
#import "SNTCodesignChecker.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTEventLog.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTRule.h"
|
||||
@@ -39,14 +43,15 @@
|
||||
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
|
||||
ruleTable:(SNTRuleTable *)ruleTable
|
||||
eventTable:(SNTEventTable *)eventTable
|
||||
notifierConnection:(SNTXPCConnection *)notifier {
|
||||
notifierConnection:(SNTXPCConnection *)notifier
|
||||
eventLog:(SNTEventLog *)eventLog {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_driverManager = driverManager;
|
||||
_ruleTable = ruleTable;
|
||||
_eventTable = eventTable;
|
||||
_notifierConnection = notifier;
|
||||
LOGI(@"Log format: Decision (A|D), Reason (B|C|S|?), SHA-256, Path, Cert SHA-256, Cert CN");
|
||||
_eventLog = eventLog;
|
||||
|
||||
// Workaround for xpcproxy/libsecurity bug on Yosemite
|
||||
// This establishes the XPC connection between libsecurity and syspolicyd.
|
||||
@@ -58,60 +63,103 @@
|
||||
|
||||
#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];
|
||||
NSString *sha256 = [binInfo SHA256];
|
||||
|
||||
// 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);
|
||||
|
||||
// Step 1 - binary rule?
|
||||
rule = [self.ruleTable binaryRuleForSHA256:sha256];
|
||||
- (santa_eventstate_t)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
|
||||
SNTRule *rule = [self.ruleTable binaryRuleForSHA256:cd.sha256];
|
||||
if (rule) {
|
||||
respondedAction = [self actionForRuleState:rule.state];
|
||||
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
|
||||
}
|
||||
|
||||
SNTCodesignChecker *csInfo = [[SNTCodesignChecker alloc] initWithBinaryPath:path];
|
||||
|
||||
// Step 2 - cert rule?
|
||||
if (!rule) {
|
||||
rule = [self.ruleTable certificateRuleForSHA256:csInfo.leafCertificate.SHA256];
|
||||
if (rule) {
|
||||
respondedAction = [self actionForRuleState:rule.state];
|
||||
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
|
||||
switch (rule.state) {
|
||||
case RULESTATE_WHITELIST:
|
||||
return EVENTSTATE_ALLOW_BINARY;
|
||||
case RULESTATE_SILENT_BLACKLIST:
|
||||
cd.silentBlock = YES;
|
||||
case RULESTATE_BLACKLIST:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return EVENTSTATE_BLOCK_BINARY;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3 - in scope?
|
||||
if (![self fileIsInScope:path]) {
|
||||
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
|
||||
[self logDecisionForEventState:EVENTSTATE_ALLOW_SCOPE sha256:sha256 path:path leafCert:nil];
|
||||
rule = [self.ruleTable certificateRuleForSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.state) {
|
||||
case RULESTATE_WHITELIST:
|
||||
return EVENTSTATE_ALLOW_CERTIFICATE;
|
||||
case RULESTATE_SILENT_BLACKLIST:
|
||||
cd.silentBlock = YES;
|
||||
case RULESTATE_BLACKLIST:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return EVENTSTATE_BLOCK_CERTIFICATE;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *msg = [self fileIsScopeBlacklisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return EVENTSTATE_BLOCK_SCOPE;
|
||||
}
|
||||
|
||||
msg = [self fileIsScopeWhitelisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return EVENTSTATE_ALLOW_SCOPE;
|
||||
}
|
||||
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case CLIENTMODE_MONITOR: return EVENTSTATE_ALLOW_UNKNOWN;
|
||||
case CLIENTMODE_LOCKDOWN: return EVENTSTATE_BLOCK_UNKNOWN;
|
||||
default: return EVENTSTATE_BLOCK_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)validateBinaryWithMessage:(santa_message_t)message {
|
||||
// Get info about the file. If we can't get this info, allow execution and log an error.
|
||||
NSError *fileInfoError;
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
|
||||
if (!binInfo) {
|
||||
LOGW(@"Failed to read file %@: %@", binInfo.path, fileInfoError.localizedDescription);
|
||||
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:message.vnode_id];
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4 - default rule :-(
|
||||
if (!rule) {
|
||||
respondedAction = [self defaultDecision];
|
||||
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
|
||||
}
|
||||
// Get codesigning info about the file.
|
||||
SNTCodesignChecker *csInfo = [[SNTCodesignChecker alloc] initWithBinaryPath:binInfo.path];
|
||||
|
||||
// Actually make the decision.
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
|
||||
cd.sha256 = binInfo.SHA256;
|
||||
cd.certCommonName = csInfo.leafCertificate.commonName;
|
||||
cd.certSHA256 = csInfo.leafCertificate.SHA256;
|
||||
cd.vnodeId = message.vnode_id;
|
||||
cd.decision = [self makeDecision:cd binaryInfo:binInfo];
|
||||
|
||||
// Save decision details for logging the execution later.
|
||||
santa_action_t action = [self actionForEventState:cd.decision];
|
||||
if (action == ACTION_RESPOND_CHECKBW_ALLOW) [self.eventLog saveDecisionDetails:cd];
|
||||
|
||||
// Get name of parent process. Do this before responding to be sure parent doesn't go away.
|
||||
char pname[PROC_PIDPATHINFO_MAXSIZE];
|
||||
proc_name(message.ppid, pname, PROC_PIDPATHINFO_MAXSIZE);
|
||||
|
||||
// Send the decision to the kernel.
|
||||
[self.driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
|
||||
|
||||
// Log to database if necessary.
|
||||
if (cd.decision != EVENTSTATE_ALLOW_BINARY &&
|
||||
cd.decision != EVENTSTATE_ALLOW_CERTIFICATE &&
|
||||
cd.decision != EVENTSTATE_ALLOW_SCOPE) {
|
||||
|
||||
// Step 5 - log to database and potentially alert user
|
||||
if (respondedAction == ACTION_RESPOND_CHECKBW_DENY ||
|
||||
!rule ||
|
||||
[[SNTConfigurator configurator] logAllEvents]) {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
se.fileSHA256 = sha256;
|
||||
se.filePath = path;
|
||||
se.occurrenceDate = [[NSDate alloc] init];
|
||||
se.fileSHA256 = cd.sha256;
|
||||
se.filePath = binInfo.path;
|
||||
se.decision = cd.decision;
|
||||
|
||||
se.signingChain = csInfo.certificates;
|
||||
se.pid = @(message.pid);
|
||||
se.ppid = @(message.ppid);
|
||||
se.parentName = @(pname);
|
||||
|
||||
se.fileBundleID = [binInfo bundleIdentifier];
|
||||
se.fileBundleName = [binInfo bundleName];
|
||||
|
||||
@@ -123,13 +171,11 @@
|
||||
se.fileBundleVersion = [binInfo bundleVersion];
|
||||
}
|
||||
|
||||
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.parentName = @(pname);
|
||||
struct passwd *user = getpwuid(message.uid);
|
||||
endpwent();
|
||||
if (user) {
|
||||
se.executingUser = @(user->pw_name);
|
||||
}
|
||||
|
||||
NSArray *loggedInUsers, *currentSessions;
|
||||
[self loggedInUsers:&loggedInUsers sessions:¤tSessions];
|
||||
@@ -138,27 +184,20 @@
|
||||
|
||||
[self.eventTable addStoredEvent:se];
|
||||
|
||||
if (respondedAction == ACTION_RESPOND_CHECKBW_DENY) {
|
||||
// If binary was blocked, do the needful
|
||||
if (action != ACTION_RESPOND_CHECKBW_ALLOW) {
|
||||
[self.eventLog logDeniedExecution:cd withMessage:message];
|
||||
|
||||
// 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
|
||||
/// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
|
||||
if (![path isEqual:@"/usr/sbin/santactl"]) {
|
||||
[self initiateEventUploadForSHA256:sha256];
|
||||
}
|
||||
[self initiateEventUploadForEvent:se];
|
||||
|
||||
if (!rule || rule.state != RULESTATE_SILENT_BLACKLIST) {
|
||||
if (!cd.silentBlock) {
|
||||
[[self.notifierConnection remoteObjectProxy] postBlockNotification:se
|
||||
withCustomMessage:rule.customMsg];
|
||||
withCustomMessage:cd.customMsg];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6 - log to log file
|
||||
[self logDecisionForEventState:[self eventStateForDecision:respondedAction type:rule.type]
|
||||
sha256:sha256
|
||||
path:path
|
||||
leafCert:csInfo.leafCertificate];
|
||||
}
|
||||
|
||||
///
|
||||
@@ -166,140 +205,80 @@
|
||||
///
|
||||
/// 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]) {
|
||||
return NO;
|
||||
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
|
||||
// Determine if file is within a whitelisted path
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Whitelist Regex";
|
||||
}
|
||||
|
||||
// If file is not a Mach-O file, we're not interested unless it's part of an install package.
|
||||
// TODO(rah): Consider adding an option to check all scripts.
|
||||
// TODO(rah): Consider adding an option to disable package script checks.
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:path];
|
||||
if (![binInfo isMachO] && ![path hasPrefix:@"/private/tmp/PKInstallSandbox."]) {
|
||||
return NO;
|
||||
if (!fi.isMachO && ![fi.path hasPrefix:@"/private/tmp/PKInstallSandbox."]) {
|
||||
return @"Not a Mach-O";
|
||||
}
|
||||
|
||||
return YES;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logDecisionForEventState:(santa_eventstate_t)eventState
|
||||
sha256:(NSString *)sha256
|
||||
path:(NSString *)path
|
||||
leafCert:(SNTCertificate *)cert {
|
||||
NSString *d, *r, *outLog;
|
||||
|
||||
switch (eventState) {
|
||||
case EVENTSTATE_ALLOW_BINARY:
|
||||
d = @"A"; r = @"B"; break;
|
||||
case EVENTSTATE_ALLOW_CERTIFICATE:
|
||||
d = @"A"; r = @"C"; break;
|
||||
case EVENTSTATE_ALLOW_SCOPE:
|
||||
d = @"A"; r = @"S"; break;
|
||||
case EVENTSTATE_ALLOW_UNKNOWN:
|
||||
d = @"A"; r = @"?"; break;
|
||||
case EVENTSTATE_BLOCK_BINARY:
|
||||
d = @"D"; r = @"B"; break;
|
||||
case EVENTSTATE_BLOCK_CERTIFICATE:
|
||||
d = @"D"; r = @"C"; break;
|
||||
case EVENTSTATE_BLOCK_UNKNOWN:
|
||||
d = @"D"; r = @"?"; break;
|
||||
default:
|
||||
d = @"?"; r = @"?"; break;
|
||||
- (NSString *)fileIsScopeBlacklisted:(SNTFileInfo *)fi {
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] blacklistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Blacklist Regex";
|
||||
}
|
||||
|
||||
// Ensure there are no pipes in the path name (as this will be confusing in the log)
|
||||
NSString *printPath = [path stringByReplacingOccurrencesOfString:@"|" withString:@"<pipe>"];
|
||||
if (fi.isMissingPageZero) return @"Missing __PAGEZERO";
|
||||
|
||||
if (cert && cert.SHA256 && cert.commonName) {
|
||||
// Also ensure there are no pipes in the cert's common name.
|
||||
NSString *printCommonName =
|
||||
[cert.commonName stringByReplacingOccurrencesOfString:@"|" withString:@"<pipe>"];
|
||||
outLog = [NSString stringWithFormat:@"%@|%@|%@|%@|%@|%@",
|
||||
d, r, sha256, printPath, cert.SHA256, printCommonName];
|
||||
} else {
|
||||
outLog = [NSString stringWithFormat:@"%@|%@|%@|%@", d, r, sha256, printPath];
|
||||
}
|
||||
|
||||
// Now make sure none of the log line has a newline in it.
|
||||
LOGI(@"%@", [[outLog componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]
|
||||
componentsJoinedByString:@" "]);
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)initiateEventUploadForSHA256:(NSString *)sha256 {
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
- (void)initiateEventUploadForEvent:(SNTStoredEvent *)event {
|
||||
// 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.
|
||||
// It's also skipped if there isn't a server configured or the last sync caused a backoff.
|
||||
if ([event.filePath isEqual:@(kSantaCtlPath)] ||
|
||||
![[SNTConfigurator configurator] syncBaseURL] ||
|
||||
[[SNTConfigurator configurator] syncBackOff]) return;
|
||||
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
exit(1);
|
||||
_exit(EPERM);
|
||||
}
|
||||
|
||||
exit(execl("/usr/sbin/santactl", "/usr/sbin/santactl", "sync",
|
||||
"singleevent", [sha256 UTF8String], NULL));
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "singleevent",
|
||||
[event.fileSHA256 UTF8String], NULL));
|
||||
}
|
||||
}
|
||||
|
||||
- (santa_action_t)defaultDecision {
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case CLIENTMODE_MONITOR: return ACTION_RESPOND_CHECKBW_ALLOW;
|
||||
case CLIENTMODE_LOCKDOWN: return ACTION_RESPOND_CHECKBW_DENY;
|
||||
default: return ACTION_RESPOND_CHECKBW_DENY; // This can't happen.
|
||||
}
|
||||
}
|
||||
|
||||
- (santa_action_t)actionForRuleState:(santa_rulestate_t)state {
|
||||
- (santa_action_t)actionForEventState:(santa_eventstate_t)state {
|
||||
switch (state) {
|
||||
case RULESTATE_WHITELIST:
|
||||
case EVENTSTATE_ALLOW_BINARY:
|
||||
case EVENTSTATE_ALLOW_CERTIFICATE:
|
||||
case EVENTSTATE_ALLOW_SCOPE:
|
||||
case EVENTSTATE_ALLOW_UNKNOWN:
|
||||
return ACTION_RESPOND_CHECKBW_ALLOW;
|
||||
case RULESTATE_BLACKLIST:
|
||||
case RULESTATE_SILENT_BLACKLIST:
|
||||
case EVENTSTATE_BLOCK_BINARY:
|
||||
case EVENTSTATE_BLOCK_CERTIFICATE:
|
||||
case EVENTSTATE_BLOCK_SCOPE:
|
||||
case EVENTSTATE_BLOCK_UNKNOWN:
|
||||
return ACTION_RESPOND_CHECKBW_DENY;
|
||||
default:
|
||||
return ACTION_ERROR;
|
||||
LOGW(@"Invalid event state %d", state);
|
||||
return ACTION_RESPOND_CHECKBW_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
- (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 +291,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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,23 +29,30 @@
|
||||
///
|
||||
|
||||
#define TSTART(testName) \
|
||||
printf(" %-50s ", testName);
|
||||
do { printf(" %-50s ", testName); } while (0)
|
||||
#define TPASS() \
|
||||
printf("\x1b[32mPASS\x1b[0m\n");
|
||||
do { printf("\x1b[32mPASS\x1b[0m\n"); } while (0)
|
||||
#define TPASSINFO(fmt, ...) \
|
||||
printf("\x1b[32mPASS\x1b[0m\n " fmt "\n", ##__VA_ARGS__);
|
||||
do { printf("\x1b[32mPASS\x1b[0m\n " fmt "\n", ##__VA_ARGS__); } while (0)
|
||||
#define TFAIL() \
|
||||
printf("\x1b[31mFAIL\x1b[0m\n"); \
|
||||
exit(1);
|
||||
do { \
|
||||
printf("\x1b[31mFAIL\x1b[0m\n"); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
#define TFAILINFO(fmt, ...) \
|
||||
printf("\x1b[31mFAIL\x1b[0m\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
|
||||
exit(1);
|
||||
do { \
|
||||
printf("\x1b[31mFAIL\x1b[0m\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
@interface SantaKernelTests : NSObject
|
||||
@property io_connect_t connection;
|
||||
@property int timesSeenLs;
|
||||
@property int timesSeenCat;
|
||||
@property int timesSeenCp;
|
||||
|
||||
@property int testExeIteration;
|
||||
@property int timesSeenTestExeIteration;
|
||||
- (void)runTests;
|
||||
@end
|
||||
|
||||
@@ -60,16 +67,15 @@
|
||||
t.standardInput = nil;
|
||||
t.standardOutput = nil;
|
||||
t.standardError = nil;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
- (NSString *)sha256ForPath:(NSString *)path {
|
||||
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
|
||||
NSData *psData = [NSData dataWithContentsOfFile:path
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:nil];
|
||||
CC_SHA256([psData bytes], (unsigned int)[psData length], sha256);
|
||||
NSData *fData = [NSData dataWithContentsOfFile:path
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:nil];
|
||||
CC_SHA256([fData bytes], (unsigned int)[fData length], sha256);
|
||||
char buf[CC_SHA256_DIGEST_LENGTH * 2 + 1];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
||||
snprintf(buf + (2*i), 4, "%02x", (unsigned char)sha256[i]);
|
||||
@@ -80,8 +86,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);
|
||||
@@ -188,8 +194,15 @@
|
||||
}
|
||||
TPASS();
|
||||
|
||||
// Fetch the SHA-256 of /bin/ps, as we'll be using that for the cache invalidation test.
|
||||
NSString *psSHA = [self sha256ForPath:@"/bin/ps"];
|
||||
// Fetch the SHA-256 of /bin/ed, as we'll be using that for the cache invalidation test.
|
||||
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
|
||||
|
||||
// Create the RE used for matching testexe's
|
||||
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
|
||||
NSString *pattern = [cwd stringByAppendingPathComponent:@"testexe\\.(\\d+)"];
|
||||
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
|
||||
options:0
|
||||
error:NULL];
|
||||
|
||||
/// Begin listening for events
|
||||
queueMemory = (IODataQueueMemory *)address;
|
||||
@@ -198,7 +211,9 @@
|
||||
dataSize = sizeof(vdata);
|
||||
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
|
||||
if (kr == kIOReturnSuccess) {
|
||||
if ([[self sha256ForPath:@(vdata.path)] isEqual:psSHA]) {
|
||||
if (vdata.action != ACTION_REQUEST_CHECKBW) continue;
|
||||
|
||||
if ([[self sha256ForPath:@(vdata.path)] isEqual:edSHA]) {
|
||||
[self postToKernelAction:ACTION_RESPOND_CHECKBW_DENY forVnodeID:vdata.vnode_id];
|
||||
} else if (strncmp("/bin/mv", vdata.path, strlen("/bin/mv")) == 0) {
|
||||
[self postToKernelAction:ACTION_RESPOND_CHECKBW_DENY forVnodeID:vdata.vnode_id];
|
||||
@@ -220,6 +235,26 @@
|
||||
}
|
||||
TPASSINFO("Received pid, ppid: %d, %d", vdata.pid, vdata.ppid);
|
||||
} else {
|
||||
NSString *path = @(vdata.path);
|
||||
|
||||
// If current executable is one of our test exe's from handlesLotsOfBinaries,
|
||||
// check that the number has increased.
|
||||
NSArray *matches = [re matchesInString:path
|
||||
options:0
|
||||
range:NSMakeRange(0, path.length)];
|
||||
if (matches.count == 1 && [matches[0] numberOfRanges] == 2) {
|
||||
NSUInteger count = [[path substringWithRange:[matches[0] rangeAtIndex:1]] intValue];
|
||||
if (count <= self.testExeIteration && count > 0) {
|
||||
self.timesSeenTestExeIteration++;
|
||||
if (self.timesSeenTestExeIteration > 2) {
|
||||
TFAILINFO("Saw same binary several times");
|
||||
}
|
||||
} else {
|
||||
self.timesSeenTestExeIteration = 0;
|
||||
self.testExeIteration = (int)count;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow everything not related to our testing.
|
||||
[self postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vdata.vnode_id];
|
||||
}
|
||||
@@ -239,11 +274,11 @@
|
||||
- (void)receiveAndBlockTests {
|
||||
TSTART("Blocks denied binaries");
|
||||
|
||||
NSTask *ps = [self taskWithPath:@"/bin/ps"];
|
||||
NSTask *ed = [self taskWithPath:@"/bin/ed"];
|
||||
|
||||
@try {
|
||||
[ps launch];
|
||||
[ps waitUntilExit];
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAIL();
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
@@ -282,12 +317,12 @@
|
||||
|
||||
// Copy the ls binary to a new file
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"santakerneltests_tmp" error:nil]) {
|
||||
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
|
||||
TFAILINFO("Failed to create temp file");
|
||||
}
|
||||
|
||||
// Launch the new file to put it in the cache
|
||||
NSTask *pwd = [self taskWithPath:@"santakerneltests_tmp"];
|
||||
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
[pwd launch];
|
||||
[pwd waitUntilExit];
|
||||
|
||||
@@ -296,28 +331,42 @@
|
||||
TFAILINFO("First launch of test binary failed");
|
||||
}
|
||||
|
||||
// Now replace the contents of the test file (which is cached) with the contents of /bin/ps,
|
||||
// Now replace the contents of the test file (which is cached) with the contents of /bin/ed,
|
||||
// which is 'blacklisted' by SHA-256 during the tests.
|
||||
FILE *infile = fopen("/bin/ps", "r");
|
||||
FILE *outfile = fopen("santakerneltests_tmp", "w");
|
||||
FILE *infile = fopen("/bin/ed", "r");
|
||||
FILE *outfile = fopen("invalidacachetest_tmp", "w");
|
||||
int ch;
|
||||
while ((ch = fgetc(infile)) != EOF) {
|
||||
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"];
|
||||
NSTask *ed = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
|
||||
@try {
|
||||
[ps launch];
|
||||
[ps waitUntilExit];
|
||||
TFAIL();
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after write while file open");
|
||||
[fm removeItemAtPath:@"invalidacachetest_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.
|
||||
ed = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
|
||||
@try {
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after file closed");
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
} @finally {
|
||||
[fm removeItemAtPath:@"santakerneltests_tmp" error:nil];
|
||||
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,12 +407,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);
|
||||
@@ -374,6 +423,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the kernel can handle _lots_ of executions.
|
||||
- (void)handlesLotsOfBinaries {
|
||||
TSTART("Handles lots of binaries");
|
||||
|
||||
const int LIMIT = 12000;
|
||||
|
||||
for (int i = 0; i < LIMIT; i++) {
|
||||
printf("\033[s"); // save cursor position
|
||||
|
||||
printf("%d/%i", i+1, LIMIT);
|
||||
|
||||
NSString *fname = [@"testexe" stringByAppendingFormat:@".%i", i];
|
||||
[[NSFileManager defaultManager] copyItemAtPath:@"/bin/hostname" toPath:fname error:NULL];
|
||||
|
||||
@try {
|
||||
NSTask *testexec = [self taskWithPath:fname];
|
||||
[testexec launch];
|
||||
[testexec waitUntilExit];
|
||||
} @catch (NSException *e) {
|
||||
TFAILINFO("Failed to launch");
|
||||
}
|
||||
|
||||
unlink([fname UTF8String]);
|
||||
printf("\033[u"); // restore cursor position
|
||||
}
|
||||
printf("\033[K\033[u"); // clear line, restore cursor position
|
||||
|
||||
TPASS();
|
||||
}
|
||||
|
||||
#pragma mark - Main
|
||||
|
||||
- (void)runTests {
|
||||
@@ -388,7 +467,7 @@
|
||||
[self performSelectorInBackground:@selector(beginListening) withObject:nil];
|
||||
|
||||
// Wait for driver to finish getting ready
|
||||
sleep(1.0);
|
||||
sleep(1);
|
||||
printf("\n-> Functional tests:\033[m\n");
|
||||
|
||||
[self receiveAndBlockTests];
|
||||
@@ -396,6 +475,7 @@
|
||||
[self invalidatesCacheTests];
|
||||
[self clearCacheTests];
|
||||
[self blocksDeniedTracedBinaries];
|
||||
[self handlesLotsOfBinaries];
|
||||
|
||||
printf("\nAll tests passed.\n\n");
|
||||
}
|
||||
|
||||
BIN
Tests/LogicTests/Resources/bad_pagezero
Executable file
BIN
Tests/LogicTests/Resources/bad_pagezero
Executable file
Binary file not shown.
BIN
Tests/LogicTests/Resources/missing_pagezero
Executable file
BIN
Tests/LogicTests/Resources/missing_pagezero
Executable file
Binary file not shown.
@@ -26,10 +26,6 @@
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
|
||||
@interface SNTExecutionController (Testing)
|
||||
- (BOOL)fileIsInScope:(NSString *)path;
|
||||
@end
|
||||
|
||||
@interface SNTExecutionControllerTest : XCTestCase
|
||||
@property id mockConfigurator;
|
||||
@property id mockCodesignChecker;
|
||||
@@ -55,13 +51,13 @@
|
||||
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
|
||||
self.mockDriverManager = OCMClassMock([SNTDriverManager class]);
|
||||
|
||||
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]);
|
||||
@@ -69,7 +65,18 @@
|
||||
self.sut = [[SNTExecutionController alloc] initWithDriverManager:self.mockDriverManager
|
||||
ruleTable:self.mockRuleDatabase
|
||||
eventTable:self.mockEventDatabase
|
||||
notifierConnection:nil];
|
||||
notifierConnection:nil
|
||||
eventLog: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 {
|
||||
@@ -83,124 +90,106 @@
|
||||
}
|
||||
|
||||
- (void)testBinaryWhitelistRule {
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
|
||||
|
||||
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = RULESTATE_WHITELIST;
|
||||
OCMExpect([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
OCMStub([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]);
|
||||
}
|
||||
|
||||
- (void)testBinaryBlacklistRule {
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
|
||||
|
||||
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = RULESTATE_BLACKLIST;
|
||||
OCMExpect([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
OCMStub([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]);
|
||||
}
|
||||
|
||||
- (void)testCertificateWhitelistRule {
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
id cert = OCMClassMock([SNTCertificate class]);
|
||||
OCMExpect([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
OCMExpect([cert SHA256]).andReturn(@"a");
|
||||
OCMStub([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
OCMStub([cert SHA256]).andReturn(@"a");
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = RULESTATE_WHITELIST;
|
||||
OCMExpect([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
OCMStub([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]);
|
||||
}
|
||||
|
||||
- (void)testCertificateBlacklistRule {
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
id cert = OCMClassMock([SNTCertificate class]);
|
||||
OCMExpect([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
OCMExpect([cert SHA256]).andReturn(@"a");
|
||||
OCMStub([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
OCMStub([cert SHA256]).andReturn(@"a");
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = RULESTATE_BLACKLIST;
|
||||
OCMExpect([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
OCMStub([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]);
|
||||
}
|
||||
|
||||
- (void)testDefaultDecision {
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([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.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]);
|
||||
}
|
||||
|
||||
- (void)testOutOfScope {
|
||||
id mockSut = OCMPartialMock(self.sut);
|
||||
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(NO);
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(NO);
|
||||
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
|
||||
[self.sut validateBinaryWithPath:@"/a/file"
|
||||
userName:@"nobody"
|
||||
pid:@(24)
|
||||
ppid:@(1)
|
||||
vnodeId:1234];
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
|
||||
[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]);
|
||||
}
|
||||
|
||||
- (void)testPageZero {
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo isMissingPageZero]).andReturn(YES);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
|
||||
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"];
|
||||
@@ -35,17 +35,24 @@
|
||||
}
|
||||
|
||||
- (void)testSHA1 {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/sbin/launchd"];
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA1);
|
||||
XCTAssertEqual(sut.SHA1.length, 40);
|
||||
XCTAssertEqualObjects(sut.SHA1, @"3a865bf47b4ceba20496e0e66e39e4cfa101ffe6");
|
||||
}
|
||||
|
||||
- (void)testSHA256 {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/sbin/launchd"];
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA256);
|
||||
XCTAssertEqual(sut.SHA256.length, 64);
|
||||
XCTAssertEqualObjects(sut.SHA256,
|
||||
@"5e089b65a1e7a4696d84a34510710b6993d1de21250c41daaec63d9981083eba");
|
||||
}
|
||||
|
||||
- (void)testExecutable {
|
||||
@@ -60,6 +67,20 @@
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testPageZero {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
path = [[NSBundle bundleForClass:[self class]] pathForResource:@"bad_pagezero" ofType:@""];
|
||||
sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/bless"];
|
||||
XCTAssertFalse(sut.isMissingPageZero);
|
||||
}
|
||||
|
||||
- (void)testKext {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:
|
||||
|
||||
@@ -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