Compare commits

...

53 Commits
0.9 ... 0.9.2

Author SHA1 Message Date
Russell Hancox
18a7992372 Config: Add more protected keys, only protect if a server is set 2015-10-02 16:35:30 -04:00
Russell Hancox
9e935f5bfb GUI: Include CFBundleName as first item in UI, if available. 2015-10-01 18:53:58 -04:00
Russell Hancox
9f49e24dc5 santad: Update file changes logging to use a configurable regex 2015-10-01 17:57:07 -04:00
Russell Hancox
dbf60f16bc santactl/sync: Fix typo causing clean sync on every run 2015-09-30 16:00:39 -04:00
Russell Hancox
0f3a228788 santactl/rule: Make help text a little clearer 2015-09-28 17:46:30 -04:00
Russell Hancox
d905f5b095 santactl/rule: Add ability to add certificate rules. Re-write argument parsing. 2015-09-28 17:20:34 -04:00
Russell Hancox
1c310486c7 santactl/status, santad: Show watchdog events in status output 2015-09-28 16:41:33 -04:00
Russell Hancox
4b01c6da91 santactl/status: Report some sync statuses. 2015-09-28 16:14:45 -04:00
Russell Hancox
5782378616 santactl/sync, santad: Add clean sync and last success options, use to initiate clean sync when database is re-created 2015-09-28 16:11:17 -04:00
Russell Hancox
64c97ebfba santad: If database open fails, delete and re-create. 2015-09-28 16:09:05 -04:00
Russell Hancox
5fd4d56b00 santactl/sync: Add ability to sync blacklist regex 2015-09-28 16:08:11 -04:00
Russell Hancox
e658b5167e Project: Update README a little 2015-09-24 18:15:03 -04:00
Russell Hancox
cea698d720 SNTCertificate: Add serialNumber and isCa properties. 2015-09-21 17:48:47 -04:00
Russell Hancox
c07f41c312 santad: Stop closing stdout/stderr 2015-09-21 15:59:32 -04:00
Russell Hancox
a837aa0334 santactl/status: Use dispatch group instead of sleeping 2015-09-21 15:59:20 -04:00
Russell Hancox
0050724e22 SNTXPCConnection: Use semaphore instead of variable & sleep. 2015-09-21 15:58:54 -04:00
Russell Hancox
adac4ac75c SantaGUI: windowWillClose and orderOut are being marked nonnull 2015-09-21 15:51:36 -04:00
Russell Hancox
718f37024a SNTConfigurator: Use NSPropertyListImmutable instead of kCFPropertyListImmutable 2015-09-21 15:51:03 -04:00
Russell Hancox
fcb3008539 Rakefile: Handle xcpretty missing better 2015-09-21 15:50:22 -04:00
Russell Hancox
8faf3eec53 santactl/sync: Validate incoming rules better 2015-09-16 15:59:50 -04:00
Russell Hancox
2bc3df3255 santad: Stop using mmap while reading files, it can be forced to crash by truncating the file. 2015-09-16 15:52:49 -04:00
Russell Hancox
5b0e550c85 santad: Add BlacklistRegex option, log a useful explanation when decision is made by scope 2015-09-16 14:19:33 -04:00
Russell Hancox
e52211abf2 santa-driver: Release proc_t acquired with proc_find. 2015-09-15 17:23:07 -04:00
Russell Hancox
9b6f231b34 santa-driver: Check for daemon earlier in FetchDecision 2015-09-14 18:20:33 -04:00
Russell Hancox
b71223705f santa-driver: If daemon fails to provide a response, print the path of the files it failed on 2015-09-14 18:19:56 -04:00
Russell Hancox
863fbe69bb santa-driver: Simplify AddToCache's locking 2015-09-14 18:19:28 -04:00
Russell Hancox
2d46279961 santa-driver: Use 0 as the client_pid when not connected 2015-09-14 18:18:51 -04:00
Russell Hancox
0d0207d77f santa-driver: lck_attr and lck_grp_attr need freeing 2015-09-14 18:18:20 -04:00
Russell Hancox
00bbade34f santa-driver: ClientConnected() should check if process is exiting/dying. 2015-09-14 18:08:57 -04:00
Russell Hancox
682f741ddc santad: Separate uid/gid fields in log. 2015-09-11 11:35:14 -04:00
Russell Hancox
3d2744c9e3 santactl/sync: Use lib compression for both preflight and event upload phases 2015-09-09 17:13:38 -04:00
Russell Hancox
cc286dcf16 santad: Fix event storage 2015-09-09 17:13:21 -04:00
Russell Hancox
27c6e2a7bd santa-driver: Don't send file mod messages unless daemon is connected 2015-09-09 14:22:31 -04:00
Russell Hancox
72c7a67ad5 Logging: Limit kernel messages to those actually sent by the kernel 2015-09-09 13:34:30 -04:00
Russell Hancox
8fe5e4e238 Logging: Update logMessage to use asl directly, adding a facility 2015-09-09 11:56:53 -04:00
Russell Hancox
02f23d0c62 santad: Add LogFileChanges option, remove LogAllEvents, fix key protection 2015-09-09 11:56:31 -04:00
Russell Hancox
ff6f4d4152 Common: Update SNTRule and SNTStoredEvent isEqual/hash/description methods. 2015-09-08 16:35:50 -04:00
Russell Hancox
2242f46792 Conf: Don't roll logs too regularly 2015-09-08 16:34:38 -04:00
Russell Hancox
642b5609b2 Tests: Fix tests after adding file write logging 2015-09-08 16:34:21 -04:00
Russell Hancox
98878f3e7c Kernel/santad: Add file write logging and exec argv's.
This necessitated a large refactoring of a bunch of code, hence being a large commit. This moves all event logging into a separate class, moves logging of executions to be from FileOp events rather than Vnode events (so we can get the argv after the execve call has finished) and implements the logging of cached execs.
2015-09-08 16:33:59 -04:00
Russell Hancox
3eb28deccf santa-driver: Verify input args are not nullptr's. 2015-09-08 14:41:34 -04:00
Russell Hancox
761a852156 santad: Always request sizeof(santa_message_t) regardless of previous message size 2015-09-08 14:40:50 -04:00
Russell Hancox
f4ddb11c1f santad: Force database permissions on startup 2015-09-08 14:33:25 -04:00
Russell Hancox
75158c11ea santa-driver: Don't create santa_message_t structs on the stack.
Also rename userId field to uid and add gid field to match
2015-08-31 15:21:25 -04:00
Russell Hancox
fe96706b0c KernelTests: Always unload kext and cleanup tmp after running 2015-08-27 18:03:40 -04:00
Russell Hancox
b87482e824 santad: Move page zero check to after binary/cert rule checks so 'bad' binaries can be whitelisted and notifications will be generated when they're blocked 2015-08-27 15:25:13 -04:00
Russell Hancox
a9ba99dc79 SNTFileInfo: Re-write mach header parsing 2015-08-27 15:25:12 -04:00
Russell Hancox
8884e92a1a Tests: Add test for missing/bad pagezero 2015-08-27 15:25:12 -04:00
Russell Hancox
6385514257 santad: Block 32-bit binaries with missing/invalid page zero 2015-08-27 15:25:12 -04:00
Russell Hancox
d3ad47022b Conf: Change log time format to ISO8601Z.3 2015-08-27 15:25:01 -04:00
Russell Hancox
138d4b507d SantaGUI: Fix fast-user-switching support. 2015-08-18 17:00:38 -04:00
Russell Hancox
3c0b195bcf Update travis.yml to add Cocoapod caching 2015-08-07 17:27:15 -04:00
Russell Hancox
d941a71bb5 Package: Forcibly make santactl symlink 2015-08-05 16:19:37 -04:00
58 changed files with 1439 additions and 562 deletions

View File

@@ -1,5 +1,6 @@
---
language: objective-c
cache: cocoapods
before_install:
- gem install cocoapods xcpretty

View File

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

View File

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

View File

@@ -14,4 +14,4 @@ SPEC CHECKSUMS:
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
COCOAPODS: 0.38.0
COCOAPODS: 0.38.2

View File

@@ -48,9 +48,23 @@ continue to work across OS versions.
Intentions and Expectations
===========================
No single system or process will stop *all* attacks, or provide 100% security. Santa is written with the intention of helping protect users from themselves. People often download malware and trust it, giving the malware credentials, or allowing unknown software to exfiltrate more data about your system. As a centrally managed component, Santa can help stop the spread of malware among a larger fleet of machines. Additionally, Santa can aid in analyzing what is running in your fleet.
No single system or process will stop *all* attacks, or provide 100% security.
Santa is written with the intention of helping protect users from themselves.
People often download malware and trust it, giving the malware credentials, or
allowing unknown software to exfiltrate more data about your system. As a
centrally managed component, Santa can help stop the spread of malware among a
larger fleet of machines. Additionally, Santa can aid in analyzing what is
running in your fleet.
Santa is part of a defense-in-depth strategy, and you should continue to protect hosts in whatever other ways you see fit.
Santa is part of a defense-in-depth strategy, and you should continue to protect
hosts in whatever other ways you see fit.
Get Help
========
If you have questions or need help getting started, the
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is the
best place to start.
Known Issues
============
@@ -58,7 +72,7 @@ Santa is not yet a 1.0 and we have some known issues to be aware of:
* Santa only blocks execution (execve and variants), it doesn't protect against
dynamic libraries loaded with dlopen, libraries on disk that have been replaced or
libraries loaded using DYLD_INSERT_LIBRARIES. We are working on also protecting
libraries loaded using `DYLD_INSERT_LIBRARIES`. We are working on also protecting
against these avenues of attack.
* Kext communication security: the kext will only accept a connection from a
@@ -107,11 +121,10 @@ and for security-reasons parts of Santa will not operate properly if not signed.
Kext Signing
============
10.9 requires a special Developer ID certificate to sign kernel extensions and
if the kext is not signed with one of these special certificates a warning will
be shown when loading the kext for the first time. In 10.10 this is a hard error
and the kext will not load at all unless the machine is booted with a debug
boot-arg.
Kernel extensions on OS X 10.9 and later must be signed using an Apple-provided
Developer ID certificate with a kernel extension flag. Without it, the only way
to load an extension is to enable kext-dev-mode or disable SIP, depending on the
OS version.
There are two possible solutions for this, for distribution purposes:
@@ -126,10 +139,6 @@ and distribute a new version of the pre-signed kext.
Apple will only grant this for broad distribution within an organization, they
won't issue them just for testing purposes.
If you just want to locally test changes to the kext code, you should enable
kext-dev mode, instructions for which can be found on the Apple developer site.
Contributing
============
Patches to this project are very much welcome. Please see the [CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)

View File

@@ -6,15 +6,19 @@ 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"
$DISABLE_XCPRETTY = false
task :default do
system("rake -sT")
end
def xcodebuild(opts)
if system "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts} | " \
"xcpretty #{XCPRETTY_DEFAULTS} && " \
"exit ${PIPESTATUS[0]}"
command = "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts}"
if not $DISABLE_XCPRETTY
command << " | xcpretty #{XCPRETTY_DEFAULTS} && exit ${PIPESTATUS[0]}"
end
if system command
puts "\e[32mPass\e[0m"
else
raise "\e[31mFail\e[0m"
@@ -28,6 +32,7 @@ task :init do
end
unless system 'xcpretty -v >/dev/null 2>&1'
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
$DISABLE_XCPRETTY = true
end
end
@@ -135,6 +140,7 @@ namespace :tests do
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

View File

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

View File

@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14E46" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8191" systemVersion="15A282b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8191"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
<connections>
<outlet property="applicationNameLabel" destination="qgf-Jf-cJr" id="1JX-X8-03v"/>
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
@@ -15,14 +16,15 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="497" height="356"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1600"/>
<rect key="contentRect" x="167" y="107" width="497" height="381"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="497" height="356"/>
<rect key="frame" x="0.0" y="0.0" width="497" height="381"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="207" y="286" width="83" height="40"/>
<rect key="frame" x="207" y="311" width="83" height="40"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
<color key="textColor" red="0.18696189413265307" green="0.18696189413265307" blue="0.18696189413265307" alpha="1" colorSpace="calibratedRGB"/>
@@ -30,10 +32,11 @@
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR">
<rect key="frame" x="22" y="239" width="454" height="17"/>
<rect key="frame" x="22" y="264" width="454" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
<animations/>
<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"/>
@@ -48,6 +51,7 @@
<constraints>
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
@@ -57,11 +61,31 @@
<binding destination="-2" name="value" keyPath="self.event.filePath" id="qfp-sR-Nmu"/>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr">
<rect key="frame" x="165" y="217" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="142" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="4hh-R2-86s"/>
</constraints>
<animations/>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="Part of SHA-256" id="X4W-9e-eIu">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
@@ -76,6 +100,7 @@
<constraints>
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Code signing information" placeholderString="" id="ztA-La-XgT">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
@@ -91,6 +116,7 @@
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
<rect key="frame" x="8" y="92" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -99,6 +125,7 @@
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J">
<rect key="frame" x="8" y="117" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -107,6 +134,7 @@
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
<rect key="frame" x="8" y="167" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -118,6 +146,7 @@
<constraints>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
</constraints>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -126,6 +155,7 @@
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y">
<rect key="frame" x="8" y="142" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -137,6 +167,7 @@
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<animations/>
<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"/>
@@ -151,10 +182,11 @@
</connections>
</textField>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="146" y="92" width="1" height="117"/>
<rect key="frame" x="146" y="92" width="1" height="142"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<animations/>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
@@ -165,6 +197,7 @@
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
</constraints>
<animations/>
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSInfo" imagePosition="overlaps" alignment="center" refusesFirstResponder="YES" imageScaling="proportionallyDown" inset="2" id="R72-Qy-Xbb">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -184,6 +217,7 @@
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<animations/>
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -201,6 +235,7 @@ DQ
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<animations/>
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -218,6 +253,7 @@ DQ
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
<animations/>
<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"/>
@@ -236,6 +272,25 @@ DQ
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC">
<rect key="frame" x="8" y="217" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
@@ -243,6 +298,7 @@ DQ
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="-116" id="6Q5-Oo-1cI"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="centerY" secondItem="qgf-Jf-cJr" secondAttribute="centerY" id="AKX-pe-hEX"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="ALv-0v-szi"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="E6D-7P-17g"/>
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
@@ -250,10 +306,12 @@ DQ
<constraint firstItem="oFj-ol-xpL" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="G0I-O2-S91"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="centerY" secondItem="oFj-ol-xpL" secondAttribute="centerY" id="GXI-pT-FM1"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="top" secondItem="pDa-fA-vnC" secondAttribute="top" id="Gd4-Nr-n5G"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="Ht4-Lg-U5N"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="IwX-ja-ZIs"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" id="JY4-N1-j8e"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" priority="500" id="JY4-N1-j8e"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="leading" priority="999" id="MVr-jY-GDj"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="750" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
@@ -261,17 +319,20 @@ DQ
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="top" secondItem="eQb-0a-76J" secondAttribute="bottom" constant="8" id="abm-cM-PN0"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" id="asc-Ga-WHD"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="eQb-0a-76J" secondAttribute="trailing" constant="20" id="b0B-3w-grH"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="centerY" secondItem="PXc-xv-A28" secondAttribute="centerY" id="cHe-pZ-0Oq"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="30" id="dYg-zP-wh2"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="eSz-lz-Fdh"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="esg-lX-BAT"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="fGd-YS-phP"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="7ua-5a-uSd" secondAttribute="trailing" constant="12" id="ioO-NJ-Jqo"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="lvJ-Rk-UT5" secondAttribute="centerY" id="jfs-YI-7Ae"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="jlD-Lo-abc"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="trailing" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" id="lse-kg-lA2"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="top" secondItem="KEB-eH-x2Y" secondAttribute="bottom" constant="8" id="m2z-1O-ifB"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
@@ -284,6 +345,7 @@ DQ
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="bottom" secondItem="C3G-wL-u7w" secondAttribute="top" constant="-8" id="zst-nc-VqA"/>
</constraints>
<animations/>
</view>
<point key="canvasLocation" x="112.5" y="308"/>
</window>

View File

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

View File

@@ -43,8 +43,8 @@
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.15f];
[[NSAnimationContext currentContext] setCompletionHandler:^{
[weakSelf.windowController windowWillClose:nil];
[weakSelf orderOut:nil];
[weakSelf.windowController windowWillClose:sender];
[weakSelf orderOut:sender];
[weakSelf setAlphaValue:1.f];
}];
[[self animator] setAlphaValue:0.f];

View File

@@ -60,4 +60,10 @@
///
@property IBOutlet NSButton *openEventButton;
///
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
///
@property IBOutlet NSTextField *applicationNameLabel;
@end

View File

@@ -46,6 +46,10 @@
[self.openEventButton setTitle:eventDetailText];
}
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
}
- (IBAction)showWindow:(id)sender {

View File

@@ -90,6 +90,16 @@
///
@property(readonly, nonatomic) NSString *orgUnit;
///
/// Is this cert a CA?
///
@property(readonly, nonatomic) BOOL isCA;
///
/// The cert serial number
///
@property(readonly, nonatomic) NSString *serialNumber;
///
/// Issuer details, same fields as above.
///

View File

@@ -357,5 +357,19 @@ static NSString *const kCertDataKey = @"certData";
}];
}
- (BOOL)isCA {
return [[self memoizedSelector:_cmd forBlock:^id{
NSDictionary *dict = [self allCertificateValues][(__bridge NSString *)kSecOIDBasicConstraints];
return [self x509ValueForLabel:@"Certificate Authority"
fromDictionary:dict];
}] isEqual:@"Yes"];
}
- (NSString *)serialNumber {
return [self memoizedSelector:_cmd forBlock:^id{
NSDictionary *dict = [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1SerialNumber];
return dict[(__bridge NSString *)kSecPropertyKeyValue];
}];
}
@end

View File

@@ -31,9 +31,13 @@ extern NSString * const kDefaultConfigFilePath;
@property(nonatomic) santa_clientmode_t clientMode;
///
/// Whether or not to log all events, even for whitelisted binaries.
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
///
@property(nonatomic) BOOL logAllEvents;
/// 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 *fileChangesRegex;
///
/// The regex of whitelisted paths. Regexes are specified in ICU format.
@@ -44,6 +48,15 @@ extern NSString * const kDefaultConfigFilePath;
///
@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
///
@@ -97,6 +110,16 @@ extern NSString * const kDefaultConfigFilePath;
///
@property(readonly, nonatomic) NSString *machineOwner;
///
/// The last date of successful sync.
///
@property(nonatomic) NSDate *syncLastSuccess;
///
/// If YES a clean sync is required.
///
@property(nonatomic) BOOL syncCleanRequired;
///
/// If set, this over-rides the default machine ID used for syncing.
///

View File

@@ -21,8 +21,10 @@
@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 *cachedFileChangesRegex;
@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;
@@ -35,8 +37,9 @@ NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
/// The keys in the config file
static NSString * const kClientModeKey = @"ClientMode";
static NSString * const kFileChangesRegexKey = @"FileChangesRegex";
static NSString * const kWhitelistRegexKey = @"WhitelistRegex";
static NSString * const kLogAllEventsKey = @"LogAllEvents";
static NSString * const kBlacklistRegexKey = @"BlacklistRegex";
static NSString * const kMoreInfoURLKey = @"MoreInfoURL";
static NSString * const kEventDetailURLKey = @"EventDetailURL";
@@ -44,6 +47,8 @@ static NSString * const kEventDetailTextKey = @"EventDetailText";
static NSString * const kDefaultBlockMessage = @"DefaultBlockMessage";
static NSString * const kSyncBaseURLKey = @"SyncBaseURL";
static NSString * const kSyncLastSuccess = @"SyncLastSuccess";
static NSString * const kSyncCleanRequired = @"SyncCleanRequired";
static NSString * const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString * const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
static NSString * const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
@@ -83,7 +88,8 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
#pragma mark Protected Keys
- (NSArray *)protectedKeys {
return @[ kClientModeKey, kWhitelistRegexKey ];
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey,
kFileChangesRegexKey, kSyncBaseURLKey ];
}
#pragma mark Public Interface
@@ -111,7 +117,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;
}
@@ -126,12 +132,45 @@ 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];
}
- (NSRegularExpression *)fileChangesRegex {
if (!self.cachedFileChangesRegex && self.configData[kFileChangesRegexKey]) {
NSString *re = self.configData[kFileChangesRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedFileChangesRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedFileChangesRegex;
}
- (void)setFileChangesRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kFileChangesRegexKey];
} else {
self.configData[kFileChangesRegexKey] = [re pattern];
}
self.cachedFileChangesRegex = nil;
[self saveConfigToDisk];
}
@@ -179,6 +218,24 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kServerAuthRootsFileKey];
}
- (NSDate *)syncLastSuccess {
return self.configData[kSyncLastSuccess];
}
- (void)setSyncLastSuccess:(NSDate *)syncLastSuccess {
self.configData[kSyncLastSuccess] = syncLastSuccess;
[self saveConfigToDisk];
}
- (BOOL)syncCleanRequired {
return [self.configData[kSyncCleanRequired] boolValue];
}
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
[self saveConfigToDisk];
}
- (NSString *)machineOwner {
NSString *machineOwner;
@@ -218,8 +275,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;
@@ -234,7 +289,7 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
NSDictionary *configData =
[NSPropertyListSerialization propertyListWithData:readData
options:kCFPropertyListImmutable
options:NSPropertyListImmutable
format:NULL
error:&error];
if (error) {
@@ -242,20 +297,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 if (self.syncBaseURL) {
// 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

View File

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

View File

@@ -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];
// 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;
} else if ([self bundle] && [self.bundle infoDictionary]) {
self.infoDict = [self.bundle infoDictionary];
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:

View File

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

View File

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

View File

@@ -14,21 +14,21 @@
#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, ...) {
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
static BOOL useSyslog = NO;
static NSString *binaryName;
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"]) {
@@ -37,7 +37,7 @@ void logMessage(int level, FILE *destination, NSString *format, ...) {
// If requested, redirect output to syslog.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
[binaryName isEqual:@"santad"]) {
strcmp(binaryName, "santad") == 0) {
useSyslog = YES;
}
});
@@ -50,16 +50,20 @@ void logMessage(int level, FILE *destination, NSString *format, ...) {
va_end(args);
if (useSyslog) {
NSString *levelName;
int syslogLevel = LOG_DEBUG;
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;
}
syslog(syslogLevel, "%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]);
}

View File

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

View File

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

View File

@@ -93,7 +93,7 @@
[connection resume];
__block BOOL verificationComplete = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[connection remoteObjectProxy] isConnectionValidWithBlock:^void(BOOL response) {
pid_t pid = self.currentConnection.processIdentifier;
@@ -107,20 +107,19 @@
self.currentConnection.exportedObject = self.exportedObject;
[self invokeAcceptedHandler];
[self.currentConnection resume];
verificationComplete = YES;
dispatch_semaphore_signal(sema);
} else {
[self invokeRejectedHandler];
[self.currentConnection invalidate];
self.currentConnection = nil;
verificationComplete = YES;
dispatch_semaphore_signal(sema);
}
}];
// Wait for validation to complete, at most 5s
for (int sleepLoops = 0; sleepLoops < 1000 && !verificationComplete; sleepLoops++) {
usleep(5000);
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self invalidate];
}
if (!verificationComplete) [self invalidate];
}
}

View File

@@ -44,9 +44,14 @@
/// Config ops
///
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
- (void)watchdogCPUEvents:(void (^)(uint64_t))reply;
- (void)watchdogRAMEvents:(void (^)(uint64_t))reply;
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;
@end

View File

@@ -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,8 +167,6 @@ 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
@@ -159,23 +174,25 @@ void SantaDecisionManager::AddToCache(
// sufficiently low, this should only ever occur if someone is purposefully
// trying to make the cache grow.
LOGI("Cache too large, flushing.");
cached_decisions_->flushCollection();
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,7 +266,7 @@ 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.
@@ -264,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;
}
@@ -279,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;
}
@@ -293,11 +311,12 @@ 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;
// Get path
@@ -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,52 @@ 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 definitely not useful.
if (ClientConnected() && !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 +478,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;

View File

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

View File

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

View File

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

View File

@@ -47,13 +47,21 @@ REGISTER_COMMAND_NAME(@"rule")
}
+ (NSString *)longHelpText {
return (@"Usage: santactl rule {add|remove} [options]\n"
@" --whitelist: add to whitelist\n"
@" --blacklist: add to blacklist\n"
@" --silent-blacklist: add to silent blacklist\n"
@" --message {message}: custom message\n"
@" --path {path}: path of binary to add\n"
@" --sha256 {sha256}: hash to add\n");
return (@"Usage: santactl rule [options]\n"
@" One of:\n"
@" --whitelist: add to whitelist\n"
@" --blacklist: add to blacklist\n"
@" --silent-blacklist: add to silent blacklist\n"
@" --remove: remove existing rule\n"
@"\n"
@" One of:\n"
@" --path {path}: path of binary/bundle to add/remove.\n"
@" Will add the hash of the file currently at that path.\n"
@" --sha256 {sha256}: hash to add/remove\n"
@"\n"
@" Optionally:\n"
@" --certificate: add certificate rule instead of binary\n"
@" --message {message}: custom message\n");
}
+ (void)printErrorUsageAndExit:(NSString *)error {
@@ -64,95 +72,72 @@ REGISTER_COMMAND_NAME(@"rule")
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
SNTConfigurator *config = [SNTConfigurator configurator];
// Ensure we have no privileges
if (!DropRootPrivileges()) {
printf("Failed to drop root privileges.\n");
exit(1);
}
if ([config syncBaseURL] != nil) {
printf("SyncBaseURL is set, rules are managed centrally.\n");
exit(1);
}
NSString *action = [arguments firstObject];
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = RULESTATE_UNKNOWN;
newRule.type = RULETYPE_BINARY;
// add or remove
if (!action) {
[self printErrorUsageAndExit:@"Missing action"];
}
NSString *path;
int state = RULESTATE_UNKNOWN;
// Parse arguments
for (NSUInteger i = 0; i < arguments.count ; i++ ) {
NSString *arg = arguments[i];
if ([action compare:@"add" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
} else if ([action compare:@"remove" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
state = RULESTATE_REMOVE;
} else {
[self printErrorUsageAndExit:@"Unknown action"];
}
NSString *customMsg = @"";
NSString *SHA256 = nil;
NSString *filePath = nil;
// parse arguments
for (NSUInteger i = 1; i < [arguments count] ; i++ ) {
NSString* argument = [arguments objectAtIndex:i];
if ([argument compare:@"--whitelist" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
state = RULESTATE_WHITELIST;
} else if ([argument compare:@"--blacklist" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
state = RULESTATE_BLACKLIST;
} else if ([argument compare:@"--silent-blacklist" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
state = RULESTATE_SILENT_BLACKLIST;
} else if ([argument compare:@"--message" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
if ([arg caseInsensitiveCompare:@"--whitelist"] == NSOrderedSame) {
newRule.state = RULESTATE_WHITELIST;
} else if ([arg caseInsensitiveCompare:@"--blacklist"] == NSOrderedSame) {
newRule.state = RULESTATE_BLACKLIST;
} else if ([arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) {
newRule.state = RULESTATE_SILENT_BLACKLIST;
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
newRule.state = RULESTATE_REMOVE;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = RULETYPE_CERT;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"No message specified"];
[self printErrorUsageAndExit:@"--path requires an argument"];
}
customMsg = [arguments objectAtIndex:i];
} else if ([argument compare:@"--path" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
path = arguments[i];
} else if ([arg caseInsensitiveCompare:@"--sha256"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"No path specified"];
[self printErrorUsageAndExit:@"--sha256 requires an argument"];
}
filePath = [arguments objectAtIndex:i];
} else if ([argument compare:@"--sha256" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
newRule.shasum = arguments[i];
if (newRule.shasum.length != 64) {
[self printErrorUsageAndExit:@"--sha256 requires a valid SHA-256 as the argument"];
}
} else if ([arg caseInsensitiveCompare:@"--message"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"No SHA-256 specified"];
[self printErrorUsageAndExit:@"--message requires an argument"];
}
SHA256 = [arguments objectAtIndex:i];
newRule.customMsg = arguments[i];
} else {
[self printErrorUsageAndExit:[@"Unknown argument: %@" stringByAppendingString:argument]];
[self printErrorUsageAndExit:[@"Unknown argument: %@" stringByAppendingString:arg]];
}
}
if (state == RULESTATE_UNKNOWN) {
if (path) {
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
if (newRule.type == RULETYPE_BINARY) {
newRule.shasum = fi.SHA256;
} else if (newRule.type == RULETYPE_CERT) {
SNTCodesignChecker *cs = [[SNTCodesignChecker alloc] initWithBinaryPath:fi.path];
newRule.shasum = cs.leafCertificate.SHA256;
}
}
if (newRule.state == RULESTATE_UNKNOWN) {
[self printErrorUsageAndExit:@"No state specified"];
}
if (filePath) {
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
if (!fileInfo) {
[self printErrorUsageAndExit:@"Provided path is not a regular file or executable bundle"];
}
SHA256 = [fileInfo SHA256];
} else if (SHA256) {
} else {
} else if (!newRule.shasum) {
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
}
SNTRule *newRule = [[SNTRule alloc] init];
newRule.shasum = SHA256;
newRule.state = state;
newRule.type = RULETYPE_BINARY;
newRule.customMsg = customMsg;
[[daemonConn remoteObjectProxy] databaseRuleAddRule:newRule cleanSlate:NO reply:^{
if (state == RULESTATE_REMOVE) {
if (newRule.state == RULESTATE_REMOVE) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);

View File

@@ -14,6 +14,7 @@
#import "SNTCommandController.h"
#import "SNTConfigurator.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@@ -41,8 +42,12 @@ REGISTER_COMMAND_NAME(@"status")
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
dispatch_group_t group = dispatch_group_create();
// Daemon status
__block NSString *clientMode;
__block uint64_t cpuEvents, ramEvents;
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] clientMode:^(santa_clientmode_t cm) {
switch (cm) {
case CLIENTMODE_MONITOR:
@@ -52,35 +57,70 @@ REGISTER_COMMAND_NAME(@"status")
default:
clientMode = [NSString stringWithFormat:@"Unknown (%d)", cm]; break;
}
dispatch_group_leave(group);
}];
do { usleep(5000); } while (!clientMode);
printf(">>> Daemon Info\n");
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] watchdogCPUEvents:^(uint64_t events) {
cpuEvents = events;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] watchdogRAMEvents:^(uint64_t events) {
ramEvents = events;
dispatch_group_leave(group);
}];
char *fileLogging = ([[SNTConfigurator configurator] fileChangesRegex] ? "Enabled" : "Disabled");
// Kext status
__block int64_t cacheCount = -1;
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] cacheCount:^(int64_t count) {
cacheCount = count;
dispatch_group_leave(group);
}];
do { usleep(5000); } while (cacheCount == -1);
printf(">>> Kernel Info\n");
printf(" %-25s | %lld\n", "Kernel cache count", cacheCount);
// Database counts
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
binaryRuleCount = binary;
certRuleCount = certificate;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] databaseEventCount:^(int64_t count) {
eventCount = count;
dispatch_group_leave(group);
}];
do { usleep(5000); } while (eventCount == -1 || binaryRuleCount == -1 || certRuleCount == -1);
// Sync status
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
NSString *lastSyncSuccess = [[[SNTConfigurator configurator] syncLastSuccess] description];
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
printf("Failed to retrieve some stats from daemon\n\n");
}
printf(">>> Daemon Info\n");
printf(" %-22s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-22s | %s\n", "File Logging", fileLogging);
printf(" %-22s | %lld\n", "Watchdog CPU Events", cpuEvents);
printf(" %-22s | %lld\n", "Watchdog RAM Events", ramEvents);
printf(">>> Kernel Info\n");
printf(" %-22s | %lld\n", "Kernel cache count", cacheCount);
printf(">>> Database Info\n");
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
printf(" %-22s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-22s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-22s | %lld\n", "Events Pending Upload", eventCount);
if (syncURLStr) {
printf(">>> Sync Info\n");
printf(" %-22s | %s\n", "Sync Server", [syncURLStr UTF8String]);
printf(" %-22s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
const char *syncDateStr = (lastSyncSuccess ? [lastSyncSuccess UTF8String] : "Never");
printf(" %-22s | %s\n", "Last Successful Sync", syncDateStr);
}
exit(0);
}

View File

@@ -31,6 +31,7 @@ extern NSString * const kClientModeMonitor;
extern NSString * const kClientModeLockdown;
extern NSString * const kCleanSync;
extern NSString * const kWhitelistRegex;
extern NSString * const kBlacklistRegex;
extern NSString * const kEvents;
extern NSString * const kFileSHA256;

View File

@@ -33,6 +33,7 @@ NSString * const kClientModeMonitor = @"MONITOR";
NSString * const kClientModeLockdown = @"LOCKDOWN";
NSString * const kCleanSync = @"clean_sync";
NSString * const kWhitelistRegex = @"whitelist_regex";
NSString * const kBlacklistRegex = @"blacklist_regex";
NSString * const kEvents = @"events";
NSString * const kFileSHA256 = @"file_sha256";

View File

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

View File

@@ -49,6 +49,13 @@
[[daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue] reply:^{}];
}
if (syncState.cleanSync) {
[[daemonConn remoteObjectProxy] setSyncCleanRequired:NO reply:^{}];
}
// Update last sync success
[[daemonConn remoteObjectProxy] setSyncLastSuccess:[NSDate date] reply:^{}];
handler(YES);
}
}] resume];

View File

@@ -17,8 +17,10 @@
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
#import "NSData+Zlib.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTConfigurator.h"
#import "SNTSystemInfo.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@@ -40,17 +42,27 @@
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
requestDict[kPrimaryUser] = syncState.machineOwner;
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"]) {
// If user requested it or we've never had a successful sync, try from a clean slate.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] ||
[[SNTConfigurator configurator] syncCleanRequired]) {
requestDict[kRequestCleanSync] = @YES;
}
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,
@@ -77,6 +89,10 @@
[[daemonConn remoteObjectProxy] setWhitelistPathRegex:r[kWhitelistRegex] reply:^{}];
}
if ([r[kBlacklistRegex] isKindOfClass:[NSString class]]) {
[[daemonConn remoteObjectProxy] setBlacklistPathRegex:r[kBlacklistRegex] reply:^{}];
}
if ([r[kCleanSync] boolValue]) {
syncState.cleanSync = YES;
}

View File

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

View File

@@ -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,27 @@
LOGI(@"Driver requested a shutdown");
exit(0);
}
case ACTION_NOTIFY_EXEC_ALLOW_NODAEMON:
case ACTION_NOTIFY_EXEC_ALLOW_CACHED:
case ACTION_NOTIFY_EXEC_DENY_CACHED: {
// TODO(rah): Implement.
case ACTION_NOTIFY_DELETE:
case ACTION_NOTIFY_EXCHANGE:
case ACTION_NOTIFY_LINK:
case ACTION_NOTIFY_RENAME:
case ACTION_NOTIFY_WRITE: {
dispatch_async(q, ^{
NSRegularExpression *re = [[SNTConfigurator configurator] fileChangesRegex];
NSString *path = @(message.path);
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
[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];
});

View 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

View 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

View File

@@ -23,6 +23,10 @@
#import "SNTRule.h"
#import "SNTRuleTable.h"
// Globals used by the santad watchdog thread
uint64_t watchdogCPUEvents = 0;
uint64_t watchdogRAMEvents = 0;
@interface SNTDaemonControlController ()
@property dispatch_source_t syncTimer;
@end
@@ -140,6 +144,16 @@
reply();
}
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setSyncLastSuccess:date];
reply();
}
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply {
[[SNTConfigurator configurator] setSyncCleanRequired:cleanReqd];
reply();
}
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply {
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
@@ -148,4 +162,20 @@
reply();
}
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply {
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
[[SNTConfigurator configurator] setBlacklistPathRegex:re];
reply();
}
- (void)watchdogCPUEvents:(void (^)(uint64_t))reply {
reply(watchdogCPUEvents);
}
- (void)watchdogRAMEvents:(void (^)(uint64_t))reply {
reply(watchdogRAMEvents);
}
@end

View File

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

View File

@@ -27,7 +27,16 @@
self = [super init];
if (self) {
[db inDatabase:^(FMDatabase *db) {
if (![db goodConnection]) {
[db close];
[[NSFileManager defaultManager] removeItemAtPath:[db databasePath] error:NULL];
[db open];
}
}];
_dbQ = db;
[self updateTableSchema];
}
return self;
@@ -51,7 +60,7 @@
uint32_t newVersion = [self initializeDatabase:db fromVersion:currentVersion];
if (newVersion < 1) return;
LOGD(@"Updated %@ from version %d to %d", [self className], currentVersion, newVersion);
LOGI(@"Updated %@ from version %d to %d", [self className], currentVersion, newVersion);
[db setUserVersion:newVersion];
}];

View File

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

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

View File

@@ -41,7 +41,6 @@
if (!event.fileSHA256 ||
!event.filePath ||
!event.occurrenceDate ||
!event.executingUser ||
!event.decision) return NO;
NSData *eventData = [NSKeyedArchiver archivedDataWithRootObject:event];

View File

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

View File

@@ -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:&currentSessions];
@@ -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,114 +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;
- (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;
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 {
if (fork() == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_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;
}
}

View File

@@ -16,6 +16,7 @@
#import "SNTCertificate.h"
#import "SNTCodesignChecker.h"
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTRule.h"
@@ -48,6 +49,8 @@
launchdSHA, @(RULESTATE_WHITELIST), @(RULETYPE_CERT)];
newVersion = 1;
[[SNTConfigurator configurator] setSyncCleanRequired:YES];
}
return newVersion;

View File

@@ -19,6 +19,9 @@
#import "SNTApplication.h"
extern uint64_t watchdogCPUEvents;
extern uint64_t watchdogRAMEvents;
/// Converts a timeval struct to double, converting the microseconds value to seconds.
static inline double timeval_to_double(struct timeval tv) {
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
@@ -59,6 +62,7 @@ void *watchdogThreadFunction(__unused void *idata) {
if (percentage > cpuWarnThreshold) {
LOGW(@"Watchdog: potentially high CPU use, ~%.2f%% over last %d seconds.",
percentage, timeInterval);
watchdogCPUEvents++;
}
// RAM
@@ -67,6 +71,7 @@ void *watchdogThreadFunction(__unused void *idata) {
double ramUseMB = (double) taskInfo.resident_size / 1024 / 1024;
if (ramUseMB > memWarnThreshold && ramUseMB > prevRamUseMB) {
LOGW(@"Watchdog: potentially high RAM use, RSS is %.2fMB.", ramUseMB);
watchdogRAMEvents++;
}
prevRamUseMB = ramUseMB;
}
@@ -87,10 +92,6 @@ int main(int argc, const char *argv[]) {
return 0;
}
// Close stdout/stderr so logging goes to syslog
fclose(stdout);
fclose(stderr);
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);
SNTApplication *s = [[SNTApplication alloc] init];

View File

@@ -211,6 +211,8 @@
dataSize = sizeof(vdata);
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
if (kr == kIOReturnSuccess) {
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) {

Binary file not shown.

Binary file not shown.

View File

@@ -114,6 +114,8 @@
@"a047a37fa2d2e118a4f5095fe074d6cfe0e352425a7632bf8659c03919a6c81d");
XCTAssertEqualObjects(sut.validFrom, [NSDate dateWithString:@"2013-04-05 15:15:55 +0000"]);
XCTAssertEqualObjects(sut.validUntil, [NSDate dateWithString:@"2015-04-04 15:15:55 +0000"]);
XCTAssertTrue(sut.isCA);
XCTAssertEqualObjects(sut.serialNumber, @"146025");
sut = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM2];
XCTAssertNotNil(sut);
@@ -128,6 +130,8 @@
@"129d39ff4384197dc2bcbe1a83a69b3405b7df33254b1b1ee29a23847a23555a");
XCTAssertEqualObjects(sut.validFrom, [NSDate dateWithString:@"2013-11-14 00:00:00 +0000"]);
XCTAssertEqualObjects(sut.validUntil, [NSDate dateWithString:@"2015-11-14 23:59:59 +0000"]);
XCTAssertFalse(sut.isCA);
XCTAssertEqualObjects(sut.serialNumber, @"5E FA 67 0E 99 E4 AB 88 E0 F2 0B 33 86 7B 78 4D");
}
- (void)testInitWithValidPEMAfterKey {

View File

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

View File

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

View File

@@ -110,4 +110,18 @@
XCTAssertNil(r);
}
- (void)testBadDatabase {
NSString *dbPath = [NSTemporaryDirectory() stringByAppendingString:@"sntruletabletest_baddb.db"];
[@"some text" writeToFile:dbPath atomically:YES encoding:NSUTF8StringEncoding error:NULL];
FMDatabaseQueue *dbq = [[FMDatabaseQueue alloc] initWithPath:dbPath];
SNTRuleTable *sut = [[SNTRuleTable alloc] initWithDatabaseQueue:dbq];
[sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:NO];
XCTAssertGreaterThan(sut.ruleCount, 0);
[[NSFileManager defaultManager] removeItemAtPath:dbPath error:NULL];
}
@end