mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
49 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 |
@@ -1,5 +1,6 @@
|
||||
---
|
||||
language: objective-c
|
||||
cache: cocoapods
|
||||
|
||||
before_install:
|
||||
- gem install cocoapods xcpretty
|
||||
|
||||
@@ -18,7 +18,7 @@ sleep 1
|
||||
sleep 1
|
||||
|
||||
# Create hopefully useful symlink for santactl
|
||||
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
/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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
|
||||
> /var/log/santa.log format="[$((Time)(utc.3))] $Message" mode=0644 rotate=seq compress file_max=10M all_max=100M uid=0 gid=0
|
||||
? [S= Message santa-driver:] claim
|
||||
? [S= Message santa-driver:] file /var/log/santa.log
|
||||
> /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
|
||||
|
||||
@@ -14,4 +14,4 @@ SPEC CHECKSUMS:
|
||||
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
|
||||
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
|
||||
|
||||
COCOAPODS: 0.38.0
|
||||
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
|
||||
|
||||
27
Rakefile
27
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']
|
||||
DSYMS = ['Santa.app.dSYM', 'santa-driver.kext.dSYM', 'santad.dSYM', 'santactl.dSYM']
|
||||
XCPRETTY_DEFAULTS = '-sc'
|
||||
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
|
||||
@@ -94,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)
|
||||
@@ -104,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"
|
||||
@@ -125,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
|
||||
|
||||
|
||||
@@ -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 */; };
|
||||
@@ -150,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 */; };
|
||||
@@ -296,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>"; };
|
||||
@@ -362,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>"; };
|
||||
@@ -462,6 +474,8 @@
|
||||
0D260DB018B68E12002A0B55 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D536ED51B8E7A2E0039A26D /* bad_pagezero */,
|
||||
0D536ED61B8E7A2E0039A26D /* missing_pagezero */,
|
||||
0D2CD4601A81C7B100C9C910 /* dn.plist */,
|
||||
0D6FDC8618C6913D0044685C /* apple.pem */,
|
||||
0D6FDC8218C68D7E0044685C /* GIAG2.crt */,
|
||||
@@ -688,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;
|
||||
@@ -942,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;
|
||||
};
|
||||
@@ -1129,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 */,
|
||||
@@ -1154,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 */,
|
||||
@@ -1245,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 */,
|
||||
@@ -1257,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;
|
||||
|
||||
@@ -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"/>
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
@@ -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"/>
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -76,16 +84,10 @@
|
||||
[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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -31,19 +31,28 @@ 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 a single line.
|
||||
/// 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
|
||||
|
||||
///
|
||||
@@ -73,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
|
||||
|
||||
///
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
@property NSString *configFilePath;
|
||||
@property NSMutableDictionary *configData;
|
||||
|
||||
/// Creating NSRegularExpression objects is not fast, so cache it.
|
||||
/// 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;
|
||||
@@ -36,11 +37,13 @@ NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
/// The keys in the config file
|
||||
static NSString * const kClientModeKey = @"ClientMode";
|
||||
static NSString * const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString * const kLogAllEventsKey = @"LogAllEvents";
|
||||
static NSString * const 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";
|
||||
@@ -82,7 +85,7 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
#pragma mark Protected Keys
|
||||
|
||||
- (NSArray *)protectedKeys {
|
||||
return @[ kClientModeKey, kWhitelistRegexKey ];
|
||||
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey, kLogFileChangesKey ];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
@@ -110,7 +113,7 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:nil];
|
||||
error:NULL];
|
||||
}
|
||||
return self.cachedWhitelistDirRegex;
|
||||
}
|
||||
@@ -125,12 +128,33 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (BOOL)logAllEvents {
|
||||
return [self.configData[kLogAllEventsKey] boolValue];
|
||||
- (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)setLogAllEvents:(BOOL)logAllEvents {
|
||||
self.configData[kLogAllEventsKey] = @(logAllEvents);
|
||||
- (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];
|
||||
}
|
||||
|
||||
@@ -146,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]];
|
||||
}
|
||||
@@ -213,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;
|
||||
|
||||
@@ -237,20 +263,29 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure no-one is trying to change protected keys behind our back.
|
||||
NSMutableDictionary *configDataMutable = [configData mutableCopy];
|
||||
BOOL changed = NO;
|
||||
for (NSString *key in self.protectedKeys) {
|
||||
if (self.configData[key] && configData[key] &&
|
||||
![self.configData[key] isEqual:configData[key]] && geteuid() == 0) {
|
||||
NSMutableDictionary *configDataMutable = [configData mutableCopy];
|
||||
configDataMutable[key] = self.configData[key];
|
||||
changed = YES;
|
||||
LOGD(@"Ignoring changed configuration key: %@", key);
|
||||
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];
|
||||
}
|
||||
self.configData = configDataMutable;
|
||||
if (changed) [self saveConfigToDisk];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
@@ -92,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,15 +20,34 @@
|
||||
#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 NSData *firstMachHeaderData;
|
||||
@property NSBundle *bundleRef;
|
||||
@property NSDictionary *infoDict;
|
||||
@property NSArray *architecturesArray;
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfo
|
||||
@@ -49,9 +68,10 @@
|
||||
}
|
||||
|
||||
_fileData = [NSData dataWithContentsOfFile:_path
|
||||
options:NSDataReadingMappedIfSafe
|
||||
options:NSDataReadingUncached
|
||||
error:error];
|
||||
if (_fileData.length == 0) return nil;
|
||||
[self parseMachHeaders];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -96,87 +116,70 @@
|
||||
}
|
||||
|
||||
- (NSArray *)architectures {
|
||||
if (!self.architecturesArray) {
|
||||
self.architecturesArray = (NSArray *)[NSNull null];
|
||||
|
||||
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 its architecture
|
||||
for (uint32_t i = 0; i < narch; ++i) {
|
||||
cpu_type_t cpu = (cpu_type_t)NSSwapBigIntToHost((unsigned int)fat_archs[i].cputype);
|
||||
[ret addObject:[self nameForCPUType:cpu]];
|
||||
}
|
||||
|
||||
self.architecturesArray = ret;
|
||||
} else if ([self firstMachHeader]) {
|
||||
struct mach_header *hdr = [self firstMachHeader];
|
||||
self.architecturesArray = @[ [self nameForCPUType:hdr->cputype] ];
|
||||
}
|
||||
}
|
||||
|
||||
return self.architecturesArray == (NSArray *)[NSNull null] ? nil : self.architecturesArray;
|
||||
return [self.machHeaders allKeys];
|
||||
}
|
||||
|
||||
- (BOOL)isDylib {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && (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 && 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 firstMachHeader] != nil;
|
||||
return ([self.machHeaders count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)isFat {
|
||||
return ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]);
|
||||
return ([self.machHeaders count] > 1);
|
||||
}
|
||||
|
||||
- (BOOL)isScript {
|
||||
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
|
||||
|
||||
///
|
||||
@@ -223,15 +226,62 @@
|
||||
if (!self.infoDict) {
|
||||
self.infoDict = (NSDictionary *)[NSNull null];
|
||||
|
||||
if ([self bundle] && [self.bundle infoDictionary]) {
|
||||
self.infoDict = [self.bundle infoDictionary];
|
||||
} else {
|
||||
// Binaries with embedded Info.plist aren't in an NSBundle but
|
||||
// CFBundleCopyInfoDictionaryForURL will return the embedded info dict.
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
|
||||
NSDictionary *infoDict =
|
||||
(__bridge_transfer NSDictionary *)CFBundleCopyInfoDictionaryForURL((__bridge CFURLRef)url);
|
||||
if (infoDict) self.infoDict = 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;
|
||||
}
|
||||
}
|
||||
return self.infoDict == (NSDictionary *)[NSNull null] ? nil : self.infoDict;
|
||||
@@ -280,46 +330,77 @@
|
||||
|
||||
#pragma mark Internal Methods
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
- (struct mach_header *)firstMachHeader {
|
||||
if (!self.firstMachHeaderData) {
|
||||
self.firstMachHeaderData = (NSData *)[NSNull null];
|
||||
- (void)parseMachHeaders {
|
||||
if (self.machHeaders) return;
|
||||
|
||||
if ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]) {
|
||||
// Get the bytes for the fat_arch
|
||||
NSData *archHdr = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
|
||||
sizeof(struct fat_arch))];
|
||||
if (!archHdr) return NULL;
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[archHdr bytes];
|
||||
// Sanity check file length
|
||||
if (self.fileData.length < sizeof(struct mach_header)) {
|
||||
self.machHeaders = [NSDictionary dictionary];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get bytes for first mach_header
|
||||
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(NSSwapBigIntToHost(fat_arch->offset),
|
||||
sizeof(struct mach_header))];
|
||||
if (!machHdr || ![self isMachHeader:(struct mach_header *)machHdr.bytes]) return NULL;
|
||||
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
|
||||
|
||||
self.firstMachHeaderData = [machHdr copy];
|
||||
} else if ([self isMachHeader:(struct mach_header *)[self.fileData bytes]]) {
|
||||
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct mach_header))];
|
||||
if (!machHdr) return NULL;
|
||||
self.firstMachHeaderData = [machHdr copy];
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (self.firstMachHeaderData == (NSData *)[NSNull null] ?
|
||||
NULL :
|
||||
(struct mach_header *)self.firstMachHeaderData.bytes);
|
||||
|
||||
self.machHeaders = [machHeaders copy];
|
||||
}
|
||||
|
||||
- (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);
|
||||
- (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;
|
||||
}
|
||||
|
||||
- (BOOL)isFatHeader:(struct fat_header *)header {
|
||||
return (header->magic == FAT_MAGIC || header->magic == FAT_CIGAM);
|
||||
///
|
||||
/// Return one of the mach_header's in this file.
|
||||
///
|
||||
- (struct mach_header *)firstMachHeader {
|
||||
return (struct mach_header *)([[[[self.machHeaders allValues] firstObject] data] bytes]);
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
@@ -335,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:
|
||||
|
||||
@@ -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,10 +32,12 @@
|
||||
|
||||
#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.
|
||||
@@ -45,7 +47,7 @@
|
||||
/// @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
|
||||
|
||||
@@ -14,25 +14,32 @@
|
||||
|
||||
#import "SNTLogging.h"
|
||||
|
||||
#import <sys/syslog.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 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, ^{
|
||||
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;
|
||||
@@ -42,25 +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;
|
||||
int syslogLevel = LOG_DEBUG;
|
||||
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"; syslogLevel = LOG_ERR; break;
|
||||
case LOG_LEVEL_WARN: levelName = @"W"; syslogLevel = LOG_WARNING; break;
|
||||
case LOG_LEVEL_INFO: levelName = @"I"; syslogLevel = LOG_INFO; break;
|
||||
case LOG_LEVEL_DEBUG: levelName = @"D"; syslogLevel = LOG_DEBUG; break;
|
||||
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;
|
||||
}
|
||||
|
||||
if (fileno(destination) == -1) {
|
||||
syslog(syslogLevel, "%s\n",
|
||||
[[NSString stringWithFormat:@"%@ %@: %@", levelName, binaryName, s] UTF8String]);
|
||||
} else {
|
||||
fprintf(destination, "%s\n",
|
||||
[[NSString stringWithFormat:@"%@ %@: %@", levelName, binaryName, s] UTF8String]);
|
||||
}
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
|
||||
asl_close(client);
|
||||
} else {
|
||||
fprintf(destination, "%s\n", [s UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,13 @@ 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());
|
||||
dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, lck_attr_alloc_init());
|
||||
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_,
|
||||
lck_attr_alloc_init());
|
||||
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);
|
||||
|
||||
@@ -52,11 +55,21 @@ void SantaDecisionManager::free() {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -80,13 +93,14 @@ void SantaDecisionManager::ConnectClient(mach_port_t port, pid_t pid) {
|
||||
|
||||
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
|
||||
@@ -101,7 +115,10 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -150,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) {
|
||||
@@ -249,11 +266,14 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
const santa_message_t message, const 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_);
|
||||
@@ -261,8 +281,9 @@ 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;
|
||||
}
|
||||
@@ -276,7 +297,7 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
// 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;
|
||||
}
|
||||
@@ -290,16 +311,14 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str) {
|
||||
santa_action_t return_action = ACTION_UNSET;
|
||||
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;
|
||||
@@ -307,31 +326,36 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
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(cred);
|
||||
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;
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
kr = dataqueue_->enqueue(&message, sizeof(message));
|
||||
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;
|
||||
}
|
||||
@@ -367,8 +391,7 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Only operate on regular files (not directories, symlinks, etc.).
|
||||
vtype vt = vnode_vtype(vp);
|
||||
if (vt != VREG) return KAUTH_RESULT_DEFER;
|
||||
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);
|
||||
@@ -400,14 +423,56 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
}
|
||||
}
|
||||
|
||||
int SantaDecisionManager::FileOpCallback(const vnode_t vp) {
|
||||
vfs_context_t context = vfs_context_create(NULL);
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
|
||||
GetVnodeIDForVnode(context, vp));
|
||||
CacheCheck(vnode_id_str);
|
||||
vfs_context_rele(context);
|
||||
return KAUTH_RESULT_DEFER;
|
||||
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
|
||||
@@ -417,17 +482,35 @@ int SantaDecisionManager::FileOpCallback(const vnode_t vp) {
|
||||
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) ||
|
||||
idata == NULL) {
|
||||
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();
|
||||
sdm->FileOpCallback(reinterpret_cast<vnode_t>(arg0));
|
||||
sdm->FileOpCallback(action, vp, path, new_path);
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
|
||||
@@ -101,9 +101,8 @@ class SantaDecisionManager : public OSObject {
|
||||
///
|
||||
/// FileOp Callback
|
||||
/// @param vp The Vnode for this request.
|
||||
/// @return int Should always be KAUTH_RESULT_DEFER.
|
||||
///
|
||||
int FileOpCallback(const vnode_t vp);
|
||||
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path, const char *new_path);
|
||||
|
||||
protected:
|
||||
///
|
||||
@@ -151,7 +150,7 @@ class SantaDecisionManager : public OSObject {
|
||||
/// @param identifier The vnode ID string for this request
|
||||
/// @return santa_action_t The response for this request
|
||||
///
|
||||
santa_action_t GetFromDaemon(const santa_message_t message,
|
||||
santa_action_t GetFromDaemon(santa_message_t *message,
|
||||
const char *identifier);
|
||||
|
||||
///
|
||||
@@ -176,7 +175,7 @@ class SantaDecisionManager : public OSObject {
|
||||
/// @param message The message to send
|
||||
/// @return bool true if sending was successful.
|
||||
///
|
||||
bool PostToQueue(santa_message_t message);
|
||||
bool PostToQueue(santa_message_t *message);
|
||||
|
||||
///
|
||||
/// Fetches the vnode_id for a given vnode.
|
||||
@@ -187,11 +186,19 @@ class SantaDecisionManager : public OSObject {
|
||||
///
|
||||
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_;
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
[config setTLSMinimumSupportedProtocol:kTLSProtocol12];
|
||||
[config setHTTPShouldUsePipelining:YES];
|
||||
return [self initWithSessionConfiguration:config];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "SNTKernelCommon.h"
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTSystemInfo.h"
|
||||
@@ -47,10 +48,18 @@
|
||||
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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#import "SNTDaemonControlController.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
#import "SNTEventLog.h"
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTExecutionController.h"
|
||||
#import "SNTFileWatcher.h"
|
||||
@@ -34,6 +35,7 @@
|
||||
|
||||
@interface SNTApplication ()
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTEventLog *eventLog;
|
||||
@property SNTEventTable *eventTable;
|
||||
@property SNTExecutionController *execController;
|
||||
@property SNTFileWatcher *configFileWatcher;
|
||||
@@ -93,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;
|
||||
}
|
||||
|
||||
@@ -119,15 +124,25 @@
|
||||
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, ^{
|
||||
[self.execController validateBinaryWithMessage:message];
|
||||
});
|
||||
|
||||
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
|
||||
@@ -35,7 +35,7 @@
|
||||
_driverManager = driverManager;
|
||||
|
||||
_syncTimer = [self createSyncTimer];
|
||||
[self rescheduleSyncSecondsFromNow:600];
|
||||
[self rescheduleSyncSecondsFromNow:30];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -46,22 +46,19 @@
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
|
||||
dispatch_source_set_event_handler(syncTimerQ, ^{
|
||||
[self rescheduleSyncSecondsFromNow:600];
|
||||
[self rescheduleSyncSecondsFromNow:600];
|
||||
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[[SNTConfigurator configurator] setSyncBackOff:NO];
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[[SNTConfigurator configurator] setSyncBackOff:NO];
|
||||
|
||||
pid_t child = fork();
|
||||
if (child == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(1);
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(EPERM);
|
||||
}
|
||||
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog", NULL));
|
||||
}
|
||||
|
||||
fclose(stdout);
|
||||
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", NULL));
|
||||
}
|
||||
});
|
||||
|
||||
dispatch_resume(syncTimerQ);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
@class SNTCodesignChecker;
|
||||
@class SNTDriverManager;
|
||||
@class SNTEventLog;
|
||||
@class SNTEventTable;
|
||||
@class SNTRuleTable;
|
||||
@class SNTXPCConnection;
|
||||
@@ -30,19 +31,20 @@
|
||||
/// + (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
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
|
||||
#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"
|
||||
@@ -41,13 +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;
|
||||
_eventLog = eventLog;
|
||||
|
||||
// Workaround for xpcproxy/libsecurity bug on Yosemite
|
||||
// This establishes the XPC connection between libsecurity and syspolicyd.
|
||||
@@ -59,68 +63,103 @@
|
||||
|
||||
#pragma mark Binary Validation
|
||||
|
||||
- (santa_eventstate_t)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
|
||||
SNTRule *rule = [self.ruleTable binaryRuleForSHA256:cd.sha256];
|
||||
if (rule) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
NSString *path = @(message.path);
|
||||
uint64_t vnodeId = message.vnode_id;
|
||||
|
||||
// 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:path error:&fileInfoError];
|
||||
NSString *sha256 = [binInfo SHA256];
|
||||
|
||||
// If we can't read the file and hash properly, log an error.
|
||||
if (!binInfo || !sha256) {
|
||||
LOGW(@"Failed to read file %@: %@", path, fileInfoError.localizedDescription);
|
||||
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
|
||||
[self logDecisionForEventState:EVENTSTATE_ALLOW_UNKNOWN sha256:nil path:path leafCert:nil];
|
||||
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;
|
||||
}
|
||||
|
||||
// These will be filled in either in later steps
|
||||
santa_action_t respondedAction = ACTION_UNSET;
|
||||
SNTRule *rule;
|
||||
// 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);
|
||||
|
||||
// Step 1 - binary rule?
|
||||
rule = [self.ruleTable binaryRuleForSHA256:sha256];
|
||||
if (rule) {
|
||||
respondedAction = [self actionForRuleState:rule.state];
|
||||
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
|
||||
}
|
||||
// Send the decision to the kernel.
|
||||
[self.driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
|
||||
|
||||
SNTCodesignChecker *csInfo = [[SNTCodesignChecker alloc] initWithBinaryPath:path];
|
||||
// Log to database if necessary.
|
||||
if (cd.decision != EVENTSTATE_ALLOW_BINARY &&
|
||||
cd.decision != EVENTSTATE_ALLOW_CERTIFICATE &&
|
||||
cd.decision != EVENTSTATE_ALLOW_SCOPE) {
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3 - in scope?
|
||||
if (!rule && ![self fileIsInScope:path]) {
|
||||
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
|
||||
[self logDecisionForEventState:EVENTSTATE_ALLOW_SCOPE sha256:sha256 path:path leafCert:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4 - default rule :-(
|
||||
if (!rule) {
|
||||
respondedAction = [self defaultDecision];
|
||||
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
|
||||
}
|
||||
|
||||
// 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];
|
||||
|
||||
@@ -132,20 +171,11 @@
|
||||
se.fileBundleVersion = [binInfo bundleVersion];
|
||||
}
|
||||
|
||||
se.signingChain = csInfo.certificates;
|
||||
se.occurrenceDate = [[NSDate alloc] init];
|
||||
se.decision = [self eventStateForDecision:respondedAction type:rule.type];
|
||||
se.pid = @(message.pid);
|
||||
se.ppid = @(message.ppid);
|
||||
se.parentName = @(pname);
|
||||
|
||||
struct passwd *user = getpwuid(message.userId);
|
||||
struct passwd *user = getpwuid(message.uid);
|
||||
endpwent();
|
||||
NSString *userName;
|
||||
if (user) {
|
||||
userName = @(user->pw_name);
|
||||
se.executingUser = @(user->pw_name);
|
||||
}
|
||||
se.executingUser = userName;
|
||||
|
||||
NSArray *loggedInUsers, *currentSessions;
|
||||
[self loggedInUsers:&loggedInUsers sessions:¤tSessions];
|
||||
@@ -154,29 +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 santactl so that
|
||||
/// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
|
||||
if (![path isEqual:@(kSantaCtlPath)] &&
|
||||
[[SNTConfigurator configurator] syncBaseURL]
|
||||
&& ![[SNTConfigurator configurator] syncBackOff]) {
|
||||
[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];
|
||||
}
|
||||
|
||||
///
|
||||
@@ -188,118 +209,68 @@
|
||||
///
|
||||
/// @return @c YES if file is in scope, @c NO otherwise.
|
||||
///
|
||||
- (BOOL)fileIsInScope:(NSString *)path {
|
||||
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
|
||||
// Determine if file is within a whitelisted path
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
|
||||
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
|
||||
return NO;
|
||||
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;
|
||||
}
|
||||
|
||||
- (santa_eventstate_t)eventStateForDecision:(santa_action_t)decision type:(santa_ruletype_t)type {
|
||||
switch (decision) {
|
||||
case ACTION_RESPOND_CHECKBW_ALLOW:
|
||||
switch (type) {
|
||||
case RULETYPE_BINARY: return EVENTSTATE_ALLOW_BINARY;
|
||||
case RULETYPE_CERT: return EVENTSTATE_ALLOW_CERTIFICATE;
|
||||
default: return EVENTSTATE_ALLOW_UNKNOWN;
|
||||
|
||||
}
|
||||
case ACTION_RESPOND_CHECKBW_DENY:
|
||||
switch (type) {
|
||||
case RULETYPE_BINARY: return EVENTSTATE_BLOCK_BINARY;
|
||||
case RULETYPE_CERT: return EVENTSTATE_BLOCK_CERTIFICATE;
|
||||
default: return EVENTSTATE_BLOCK_UNKNOWN;
|
||||
}
|
||||
default: return EVENTSTATE_UNKNOWN;
|
||||
- (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";
|
||||
}
|
||||
|
||||
if (fi.isMissingPageZero) return @"Missing __PAGEZERO";
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
// Ensure there are no pipes in the path name (as this will be confusing in the log)
|
||||
NSString *printPath = [path stringByReplacingOccurrencesOfString:@"|" withString:@"<pipe>"];
|
||||
|
||||
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:@" "]);
|
||||
}
|
||||
|
||||
- (void)initiateEventUploadForSHA256:(NSString *)sha256 {
|
||||
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(kSantaCtlPath, kSantaCtlPath, "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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]);
|
||||
@@ -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,10 +331,10 @@
|
||||
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);
|
||||
@@ -307,13 +342,13 @@
|
||||
fclose(infile);
|
||||
|
||||
// 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];
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAILINFO("Launched after write while file open");
|
||||
[fm removeItemAtPath:@"santakerneltests_tmp" error:nil];
|
||||
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
|
||||
} @catch (NSException *exception) {
|
||||
// This is a pass, but we have more to do.
|
||||
}
|
||||
@@ -322,16 +357,16 @@
|
||||
fclose(outfile);
|
||||
|
||||
// And try running the temp file again. If it succeeds, the test failed.
|
||||
ps = [self taskWithPath:@"santakerneltests_tmp"];
|
||||
ed = [self taskWithPath:@"invalidacachetest_tmp"];
|
||||
|
||||
@try {
|
||||
[ps launch];
|
||||
[ps waitUntilExit];
|
||||
[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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,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 {
|
||||
@@ -402,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];
|
||||
@@ -410,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,7 +51,6 @@
|
||||
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
|
||||
self.mockDriverManager = OCMClassMock([SNTDriverManager class]);
|
||||
|
||||
@@ -70,7 +65,8 @@
|
||||
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.
|
||||
@@ -94,14 +90,12 @@
|
||||
}
|
||||
|
||||
- (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 validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -110,14 +104,12 @@
|
||||
}
|
||||
|
||||
- (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 validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -126,16 +118,16 @@
|
||||
}
|
||||
|
||||
- (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 validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -144,18 +136,16 @@
|
||||
}
|
||||
|
||||
- (void)testCertificateBlacklistRule {
|
||||
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");
|
||||
|
||||
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 validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -164,16 +154,14 @@
|
||||
}
|
||||
|
||||
- (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.mockFileInfo SHA256]).andReturn(@"a");
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_MONITOR);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:1234]);
|
||||
|
||||
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
|
||||
@@ -181,10 +169,9 @@
|
||||
}
|
||||
|
||||
- (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);
|
||||
OCMStub([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
|
||||
forVnodeID:1234]);
|
||||
@@ -194,7 +181,15 @@
|
||||
[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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user