mirror of
https://github.com/google/santa.git
synced 2026-01-22 04:28:11 -05:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8640feafe | ||
|
|
e94e9e2be4 | ||
|
|
4053aac365 | ||
|
|
a5fa6c7aef | ||
|
|
97263894d1 | ||
|
|
1885580958 | ||
|
|
1167b470bb | ||
|
|
7600506d6d | ||
|
|
86bad866a0 | ||
|
|
2f1a15cf7e | ||
|
|
52b0e1870f | ||
|
|
9b181c1e0d | ||
|
|
100f2dc45e | ||
|
|
b247c3d477 | ||
|
|
76ee82b258 | ||
|
|
e8fcd29669 | ||
|
|
8dd16ecea4 | ||
|
|
e9c0bcd877 | ||
|
|
75ed4b52a6 | ||
|
|
71635c00df | ||
|
|
1810af5483 | ||
|
|
b07835dfd5 | ||
|
|
4c33aa2aae | ||
|
|
3c255640cb | ||
|
|
3d08ba9ebc | ||
|
|
f64482500e | ||
|
|
215902f192 | ||
|
|
3e9c3a069d | ||
|
|
841fb48479 | ||
|
|
df8e41925f |
@@ -2,12 +2,12 @@ PODS:
|
||||
- FMDB (2.6.2):
|
||||
- FMDB/standard (= 2.6.2)
|
||||
- FMDB/standard (2.6.2)
|
||||
- MOLAuthenticatingURLSession (1.6):
|
||||
- MOLAuthenticatingURLSession (1.8):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- MOLCertificate (1.5)
|
||||
- MOLCodesignChecker (1.5):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- OCMock (3.3)
|
||||
- OCMock (3.3.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
@@ -18,10 +18,10 @@ DEPENDENCIES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
|
||||
MOLAuthenticatingURLSession: f956240458fb24b61e5607d735948dc9babfb4e3
|
||||
MOLAuthenticatingURLSession: d04d93e7fe209533befb3d0e70a6675aa7f21d5a
|
||||
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
|
||||
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
|
||||
OCMock: d68685bde31f69cb61d518dcb39269080c78b5ed
|
||||
OCMock: f3f61e6eaa16038c30caa5798c5e49d3307b6f22
|
||||
|
||||
PODFILE CHECKSUM: bc456d69693ca262c781dbbde40529a9474b84b5
|
||||
|
||||
|
||||
86
README.md
86
README.md
@@ -16,25 +16,53 @@ managing the system and synchronizing the database with a server.
|
||||
Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
|
||||
and finishing up a security audit.
|
||||
|
||||
Santa is named because it keeps track of binaries that are naughty and nice.
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
Santa is a project of Google's Macintosh Operations Team.
|
||||
|
||||
Features
|
||||
Admin-Related Features
|
||||
========
|
||||
|
||||
* Multiple modes: MONITOR and LOCKDOWN. In MONITOR mode all binaries except
|
||||
those marked as blacklisted will be allowed to run, whilst being logged and
|
||||
recorded in the database. In LOCKDOWN mode, only whitelisted binaries are
|
||||
* Multiple modes: In the default MONITOR mode, all binaries except
|
||||
those marked as blacklisted will be allowed to run, whilst being logged and recorded in the events database. In LOCKDOWN mode, only whitelisted binaries are
|
||||
allowed to run.
|
||||
|
||||
* Codesign listing: Binaries can be whitelisted/blacklisted by their signing
|
||||
certificate, so you can trust/block all binaries by a given publisher. The
|
||||
binary will only be whitelisted by certificate if its signature validates
|
||||
correctly. However, a decision for a binary will override a decision for a
|
||||
certificate; i.e. you can whitelist a certificate while blacklisting a binary
|
||||
signed by that certificate or vice-versa.
|
||||
* Event logging: When the kext is loaded, all binary launches are logged.
|
||||
When in either mode, all unknown or denied binaries are stored in the database to enable later aggregation.
|
||||
|
||||
* Certificate-based rules, with override levels: Instead of relying on a binaries hash (or 'fingerprint'), executables can be whitelisted/blacklisted by their signing
|
||||
certificate. You can therefore trust/block all binaries by a given publisher that were signed with that cert across version updates. A
|
||||
binary can only be whitelisted by its certificate if its signature validates
|
||||
correctly, but a rule for a binaries fingerprint will override a decision for a
|
||||
certificate; i.e. you can whitelist a certificate while blacklisting a binary
|
||||
signed with that certificate, or vice-versa.
|
||||
|
||||
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature as Managed Client for OS X's (the precursor to configuration profiles, which used the same implementation mechanism) Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and doesn't rely on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precendence.
|
||||
|
||||
* Failsafe cert rules: You cannot put in a deny rule that would block the certificate used to sign launchd, a.k.a. pid 1, and therefore all components used in macOS. The binaries in every OS update (and in some cases entire new versions) are therefore auto-whitelisted. This does not affect binaries from Apple's App Store, which use various certs that change regularly for common apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct separate cert than other Google apps.
|
||||
|
||||
Intentions and Expectations
|
||||
===========================
|
||||
No single system or process will stop *all* attacks, or provide 100% security.
|
||||
Santa is written with the intention of helping protect users from themselves.
|
||||
People often download malware and trust it, giving the malware credentials, or
|
||||
allowing unknown software to exfiltrate more data about your system. As a
|
||||
centrally managed component, Santa can help stop the spread of malware among a
|
||||
larger fleet of machines. Independently, Santa can aid in analyzing what is
|
||||
running on your computer.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to protect
|
||||
hosts in whatever other ways you see fit.
|
||||
|
||||
Get Help
|
||||
========
|
||||
|
||||
If you have questions or otherwise need help getting started, the
|
||||
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
|
||||
great place. Please consult the [wiki](https://github.com/google/santa/wiki) and [issues](https://github.com/google/santa/issues) as well.
|
||||
|
||||
Security and Performance-Related Features
|
||||
============
|
||||
* In-kernel caching: whitelisted binaries are cached in the kernel so the
|
||||
processing required to make a request is only done if the binary
|
||||
isn't already cached.
|
||||
@@ -44,42 +72,17 @@ daemon, the GUI agent and the command-line utility) communicate with each other
|
||||
using XPC and check that their signing certificates are identical before any
|
||||
communication is accepted.
|
||||
|
||||
* Event logging: all executions processed by the userland agent are logged and
|
||||
all unknown or denied binaries are also stored in the database for upload to a
|
||||
server.
|
||||
|
||||
* Kext uses only KPIs: the kernel extension only uses provided kernel
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
============
|
||||
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
|
||||
against these avenues of attack.
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been replaced, or
|
||||
libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version 0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were exploited in some versions of macOS. We are working on also protecting against similar avenues of attack.
|
||||
|
||||
* Kext communication security: the kext will only accept a connection from a
|
||||
single client at a time and said client must be running as root. We haven't yet
|
||||
@@ -89,9 +92,8 @@ found a good way to ensure the kext only accepts connections from a valid client
|
||||
only the root user can read/write it. We're considering approaches to secure
|
||||
this further.
|
||||
|
||||
* Sync client: the command-line client includes a command to synchronize with a
|
||||
management server, including the uploading of events that have occurred on the
|
||||
machine and to download new rules. We're still very heavily working on this
|
||||
* Sync client: The `santactl` command-line client includes a flag to synchronize with a management server, which uploads events that have occurred on the
|
||||
machine and downloads new rules. We're still very heavily working on this
|
||||
server (which is AppEngine-based and will be open-sourced in the future), so the
|
||||
sync client code is unfinished. It does show the 'API' that we're expecting to
|
||||
use so if you'd like to write your own management server, feel free to look at
|
||||
@@ -104,7 +106,7 @@ of temporary generated scripts, which we can't possibly whitelist and not doing
|
||||
so would cause problems. We're happy to revisit this (or at least make it an
|
||||
option) if it would be useful to others.
|
||||
|
||||
* Documentation: There currently isn't any.
|
||||
* Documentation: This is currently limited.
|
||||
|
||||
* Tests: There aren't enough of them.
|
||||
|
||||
|
||||
1
Rakefile
1
Rakefile
@@ -55,6 +55,7 @@ desc "Clean"
|
||||
task :clean => :init do
|
||||
puts "Cleaning"
|
||||
FileUtils.rm_rf(OUTPUT_PATH)
|
||||
xcodebuild("-scheme All clean")
|
||||
end
|
||||
|
||||
# Build
|
||||
|
||||
@@ -171,13 +171,12 @@
|
||||
1C299D1C789489996FF9E081 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 87D1CEAEDF1FA6819A855559 /* libPods-Santa.a */; };
|
||||
29C490B1720D4FD576F93519 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17D03B346587131C45A8DA67 /* libPods-LogicTests.a */; };
|
||||
2BA4AE89AA2447E29DA2E85C /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BE53E1EAE84D54E7FCB22FD5 /* libPods-santactl.a */; };
|
||||
34D8F6C53153950A66DBEF69 /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 60A9B66BB0F7404D1F61D518 /* libPods-santad.a */; };
|
||||
4092327A1A51B66400A04527 /* SNTCommandRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 409232791A51B65D00A04527 /* SNTCommandRule.m */; };
|
||||
5727587C816713451860B968 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 873978BCE4B0DBD2A89C99D1 /* libPods-LogicTests.a */; };
|
||||
8E9C0BF5D66552F66CC88CD9 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2B9044B79DD2E4DEC5D3B7A /* libPods-Santa.a */; };
|
||||
580046F725A5D0874B970A17 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2B9044B79DD2E4DEC5D3B7A /* libPods-Santa.a */; };
|
||||
79C1556E6EAC94038762EF36 /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 556108C12FC29E329D82D4CB /* libPods-santactl.a */; };
|
||||
A60673DE57680AC450A3B0B2 /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BE438428F17C09C6A9D0802 /* libPods-santad.a */; };
|
||||
B6724720BE0366937D375488 /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 60A9B66BB0F7404D1F61D518 /* libPods-santad.a */; };
|
||||
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
|
||||
F0AC4437ADEC6E17BDC3761F /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 556108C12FC29E329D82D4CB /* libPods-santactl.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -433,7 +432,6 @@
|
||||
0D3AFBF618FB4C7E0087BCEE /* Cocoa.framework in Frameworks */,
|
||||
0D3AFBF818FB4C870087BCEE /* IOKit.framework in Frameworks */,
|
||||
29C490B1720D4FD576F93519 /* libPods-LogicTests.a in Frameworks */,
|
||||
5727587C816713451860B968 /* libPods-LogicTests.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -446,7 +444,7 @@
|
||||
0D35BDBD18FDA23600921A21 /* IOKit.framework in Frameworks */,
|
||||
0D35BD9F18FD71CE00921A21 /* Foundation.framework in Frameworks */,
|
||||
2BA4AE89AA2447E29DA2E85C /* libPods-santactl.a in Frameworks */,
|
||||
F0AC4437ADEC6E17BDC3761F /* libPods-santactl.a in Frameworks */,
|
||||
79C1556E6EAC94038762EF36 /* libPods-santactl.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -459,7 +457,7 @@
|
||||
0D8C200C180F359A00CE2BF8 /* Security.framework in Frameworks */,
|
||||
0D385DB8180DE4A900418BC6 /* Cocoa.framework in Frameworks */,
|
||||
1C299D1C789489996FF9E081 /* libPods-Santa.a in Frameworks */,
|
||||
8E9C0BF5D66552F66CC88CD9 /* libPods-Santa.a in Frameworks */,
|
||||
580046F725A5D0874B970A17 /* libPods-Santa.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -471,7 +469,7 @@
|
||||
0D4A5007176A4602004F63BF /* Security.framework in Frameworks */,
|
||||
0D9A7F3F1759330500035EB5 /* Foundation.framework in Frameworks */,
|
||||
A60673DE57680AC450A3B0B2 /* libPods-santad.a in Frameworks */,
|
||||
B6724720BE0366937D375488 /* libPods-santad.a in Frameworks */,
|
||||
34D8F6C53153950A66DBEF69 /* libPods-santad.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -857,15 +855,12 @@
|
||||
buildConfigurationList = 0D260DBC18B68E12002A0B55 /* Build configuration list for PBXNativeTarget "LogicTests" */;
|
||||
buildPhases = (
|
||||
376E230296F6EA7A4DA8BBF0 /* [CP] Check Pods Manifest.lock */,
|
||||
C88551472F6983D0B00880F8 /* [CP] Check Pods Manifest.lock */,
|
||||
0D673DAD18FC9017009C5B06 /* Delete existing coverage files */,
|
||||
0D260DA818B68E12002A0B55 /* Sources */,
|
||||
0D260DA918B68E12002A0B55 /* Frameworks */,
|
||||
0D260DAA18B68E12002A0B55 /* Resources */,
|
||||
1D12555F0F4EF323B11E40F9 /* [CP] Embed Pods Frameworks */,
|
||||
0C5C7A6AB763BCE7F760FAFF /* [CP] Copy Pods Resources */,
|
||||
14A598861D1307F88A43E84B /* 📦 Embed Pods Frameworks */,
|
||||
6695BC8CEF4D9E1DC8BCD43E /* 📦 Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -881,12 +876,10 @@
|
||||
buildConfigurationList = 0D35BDA918FD71CE00921A21 /* Build configuration list for PBXNativeTarget "santactl" */;
|
||||
buildPhases = (
|
||||
5F1504EA0D172767F2BCAAEB /* [CP] Check Pods Manifest.lock */,
|
||||
EE6ABBFEC2708CE57EB1780A /* [CP] Check Pods Manifest.lock */,
|
||||
0DD98E671A5DD02000A754C6 /* Update Version Info */,
|
||||
0D35BD9A18FD71CE00921A21 /* Sources */,
|
||||
0D35BD9B18FD71CE00921A21 /* Frameworks */,
|
||||
E50DD7319E04737B040B69EC /* [CP] Copy Pods Resources */,
|
||||
ED7B6914325B2B429B83C595 /* 📦 Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -902,15 +895,12 @@
|
||||
buildConfigurationList = 0D385DE3180DE4A900418BC6 /* Build configuration list for PBXNativeTarget "Santa" */;
|
||||
buildPhases = (
|
||||
373591F801D7B22635DAD7A0 /* [CP] Check Pods Manifest.lock */,
|
||||
B34356D1FD4C8C6EF75F2FFE /* [CP] Check Pods Manifest.lock */,
|
||||
0DD98E681A5DD03E00A754C6 /* Update Version Info */,
|
||||
0D385DB2180DE4A900418BC6 /* Sources */,
|
||||
0D385DB3180DE4A900418BC6 /* Frameworks */,
|
||||
0D385DB4180DE4A900418BC6 /* Resources */,
|
||||
31CD7EDCDEBD95322ED67F63 /* [CP] Embed Pods Frameworks */,
|
||||
B3EB60284D47F89140F5A033 /* [CP] Copy Pods Resources */,
|
||||
D21E717BD1BF6B67486244B1 /* 📦 Embed Pods Frameworks */,
|
||||
62408678C93447E3CE5B0C72 /* 📦 Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -947,13 +937,11 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0D9A7F471759330500035EB5 /* Build configuration list for PBXNativeTarget "santad" */;
|
||||
buildPhases = (
|
||||
44898C277E6A668A58B5E7A8 /* [CP] Check Pods Manifest.lock */,
|
||||
A3D478EF1D48EA118AF176E9 /* [CP] Check Pods Manifest.lock */,
|
||||
0DD98E661A5DCED300A754C6 /* Update Version Info */,
|
||||
0D9A7F391759330400035EB5 /* Sources */,
|
||||
0D9A7F3A1759330400035EB5 /* Frameworks */,
|
||||
435B0E246EE25ACC763D684C /* [CP] Copy Pods Resources */,
|
||||
B7DCDD8443739174F2B53ECF /* 📦 Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1139,21 +1127,6 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "GIT_TAG=$(git describe --abbrev=0 --tags)\nsed -i '' \"s/TO.BE.FILLED/${GIT_TAG}/\" ${DERIVED_FILE_DIR}/santa-driver_info.c";
|
||||
};
|
||||
14A598861D1307F88A43E84B /* 📦 Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
1D12555F0F4EF323B11E40F9 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1229,21 +1202,6 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santad/Pods-santad-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
44898C277E6A668A58B5E7A8 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
5F1504EA0D172767F2BCAAEB /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1259,36 +1217,6 @@
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
62408678C93447E3CE5B0C72 /* 📦 Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Santa/Pods-Santa-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
6695BC8CEF4D9E1DC8BCD43E /* 📦 Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A3D478EF1D48EA118AF176E9 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1304,21 +1232,6 @@
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B34356D1FD4C8C6EF75F2FFE /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B3EB60284D47F89140F5A033 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1334,51 +1247,6 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Santa/Pods-Santa-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B7DCDD8443739174F2B53ECF /* 📦 Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santad/Pods-santad-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
C88551472F6983D0B00880F8 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
D21E717BD1BF6B67486244B1 /* 📦 Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Santa/Pods-Santa-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E50DD7319E04737B040B69EC /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1394,36 +1262,6 @@
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santactl/Pods-santactl-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
ED7B6914325B2B429B83C595 /* 📦 Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santactl/Pods-santactl-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EE6ABBFEC2708CE57EB1780A /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<development version="6300" identifier="xcode"/>
|
||||
@@ -321,7 +321,7 @@ DQ
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<buttonCell key="cell" type="roundTextured" title="Ignore" bezelStyle="texturedRounded" alignment="center" 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"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
|
||||
@@ -92,9 +92,16 @@
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr;
|
||||
NSString *formatStr, *versionStr;
|
||||
if (config.eventDetailBundleURL && event.fileBundleID) {
|
||||
formatStr = config.eventDetailBundleURL;
|
||||
versionStr = event.fileBundleVersion;
|
||||
if (!versionStr) versionStr = event.fileBundleVersionString;
|
||||
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
|
||||
withString:event.fileBundleID];
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
|
||||
withString:versionStr];
|
||||
} else {
|
||||
formatStr = config.eventDetailURL;
|
||||
}
|
||||
@@ -113,14 +120,6 @@
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
if (event.fileBundleID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
|
||||
withString:event.fileBundleID];
|
||||
}
|
||||
if (event.fileBundleVersionString) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
|
||||
withString:event.fileBundleVersionString];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
}
|
||||
|
||||
@@ -84,6 +84,11 @@
|
||||
///
|
||||
- (BOOL)isDylib;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a bundle executable (QuickLook/Spotlight plugin, etc.)
|
||||
///
|
||||
- (BOOL)isBundle;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a kernel extension.
|
||||
///
|
||||
|
||||
@@ -196,22 +196,26 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return [self.machHeaders allKeys];
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
- (uint32_t)machFileType {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_EXECUTE) return YES;
|
||||
return NO;
|
||||
if (mach_header) return mach_header->filetype;
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
return [self machFileType] == MH_EXECUTE;
|
||||
}
|
||||
|
||||
- (BOOL)isDylib {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_DYLIB) return YES;
|
||||
return NO;
|
||||
return [self machFileType] == MH_DYLIB;
|
||||
}
|
||||
|
||||
- (BOOL)isBundle {
|
||||
return [self machFileType] == MH_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isKext {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) return YES;
|
||||
return NO;
|
||||
return [self machFileType] == MH_KEXT_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
@@ -397,7 +401,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
NSData *fatHeader = [self safeSubdataWithRange:range];
|
||||
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
|
||||
|
||||
if (fatHeader && (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM)) {
|
||||
if (fatHeader && (fh->magic == FAT_CIGAM || fh->magic == FAT_MAGIC)) {
|
||||
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];
|
||||
@@ -530,7 +534,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
/// is not the one who downloaded the file.
|
||||
///
|
||||
- (NSDictionary *)quarantineData {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir) {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir && NSURLQuarantinePropertiesKey) {
|
||||
self.quarantineDict = (NSDictionary *)[NSNull null];
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path];
|
||||
|
||||
@@ -43,8 +43,9 @@
|
||||
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
|
||||
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
|
||||
@@ -91,8 +91,9 @@ template<class T> class SantaCache {
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
if (entry->key == key) {
|
||||
T val = entry->value;
|
||||
unlock(bucket);
|
||||
return entry->value;
|
||||
return val;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ bool SantaDecisionManager::init() {
|
||||
|
||||
client_pid_ = 0;
|
||||
|
||||
ts_ = { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
|
||||
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -250,6 +253,13 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
|
||||
auto return_action = ACTION_UNSET;
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_sec_t secs = 0;
|
||||
clock_usec_t microsecs = 0;
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
uint64_t uptime = (secs * 1000000) + microsecs;
|
||||
#endif
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Add pending request to cache, to be replaced by daemon with actual response
|
||||
@@ -257,20 +267,13 @@ santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uin
|
||||
|
||||
// Send request to daemon...
|
||||
if (!PostToDecisionQueue(message)) {
|
||||
OSIncrementAtomic(&failed_decision_queue_requests_);
|
||||
if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
client_pid_ = 0;
|
||||
}
|
||||
LOGE("Failed to queue request for %s.", message->path);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
do {
|
||||
IOSleep(kRequestLoopSleepMilliseconds);
|
||||
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
|
||||
return_action = GetFromCache(identifier);
|
||||
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
|
||||
} while (!RESPONSE_VALID(return_action) && ClientConnected());
|
||||
@@ -283,21 +286,31 @@ santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uin
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
clock_get_system_microtime(&secs, µsecs);
|
||||
LOGD("Decision time: %4lldms (%s)",
|
||||
(((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
|
||||
#endif
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str) {
|
||||
const uint64_t vnode_id) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id);
|
||||
|
||||
// If item was in cache return it.
|
||||
if (RESPONSE_VALID(return_action)) return return_action;
|
||||
if (RESPONSE_VALID(return_action)) {
|
||||
return return_action;
|
||||
} else if (return_action == ACTION_REQUEST_BINARY) {
|
||||
msleep((void *)vnode_id, NULL, 0, "", &ts_);
|
||||
return FetchDecision(cred, vp, vnode_id);
|
||||
}
|
||||
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
@@ -321,6 +334,14 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (++failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d decision requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
client_pid_ = 0;
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
@@ -329,7 +350,7 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (OSCompareAndSwap(0, 1, &failed_log_queue_requests_)) {
|
||||
if (failed_log_queue_requests_++ == 0) {
|
||||
LOGW("Dropping log queue messages");
|
||||
}
|
||||
// If enqueue failed, pop an item off the queue and try again.
|
||||
@@ -337,7 +358,9 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
log_dataqueue_->dequeue(0, &dataSize);
|
||||
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
} else {
|
||||
OSCompareAndSwap(1, 0, &failed_log_queue_requests_);
|
||||
if (failed_log_queue_requests_ > 0) {
|
||||
failed_log_queue_requests_--;
|
||||
}
|
||||
}
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return kr;
|
||||
@@ -362,13 +385,11 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
// Only operate on regular files (not directories, symlinks, etc.).
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Get ID for the vnode and convert it to a string.
|
||||
// Get ID for the vnode
|
||||
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
char vnode_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
// Fetch decision
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id);
|
||||
|
||||
// If file has dirty blocks, remove from cache and deny. This would usually
|
||||
// be the case if a file has been written to and flushed but not yet
|
||||
@@ -410,8 +431,6 @@ void SantaDecisionManager::FileOpCallback(
|
||||
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);
|
||||
RemoveFromCache(vnode_id);
|
||||
} else if (action == KAUTH_FILEOP_EXEC) {
|
||||
auto message = NewMessage(nullptr);
|
||||
@@ -432,7 +451,8 @@ void SantaDecisionManager::FileOpCallback(
|
||||
|
||||
// Filter out modifications to locations that are definitely
|
||||
// not useful or made by santad.
|
||||
if (proc_selfpid() != client_pid_ &&
|
||||
if (client_pid_ > 0 &&
|
||||
proc_selfpid() != client_pid_ &&
|
||||
!strprefix(path, "/.") &&
|
||||
!strprefix(path, "/dev")) {
|
||||
auto message = NewMessage(nullptr);
|
||||
|
||||
@@ -52,8 +52,8 @@ class SantaDecisionManager : public OSObject {
|
||||
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
|
||||
|
||||
/**
|
||||
Called by SantaDriverClient during connection to provide the shared
|
||||
dataqueue memory to the client for the logging queue.
|
||||
Called by SantaDriverClient during connection to provide the shared
|
||||
dataqueue memory to the client for the logging queue.
|
||||
*/
|
||||
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
|
||||
|
||||
@@ -131,10 +131,10 @@ class SantaDecisionManager : public OSObject {
|
||||
|
||||
protected:
|
||||
/**
|
||||
While waiting for a response from the daemon, this is the number of
|
||||
While waiting for a response from the daemon, this is the maximum number of
|
||||
milliseconds to sleep for before checking the cache for a response.
|
||||
*/
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 10;
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
|
||||
|
||||
/// The maximum number of milliseconds a cached deny message should be considered valid.
|
||||
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
@@ -149,7 +149,7 @@ class SantaDecisionManager : public OSObject {
|
||||
static const uint32_t kMaxDecisionQueueEvents = 512;
|
||||
|
||||
/// The maximum number of messages can be kept in the logging data queue at any time.
|
||||
static const uint32_t kMaxLogQueueEvents = 1024;
|
||||
static const uint32_t kMaxLogQueueEvents = 2048;
|
||||
|
||||
/**
|
||||
Fetches a response from the daemon. Handles both daemon death
|
||||
@@ -170,10 +170,10 @@ class SantaDecisionManager : public OSObject {
|
||||
@param cred The credential for this request.
|
||||
@param vp The Vnode for this request.
|
||||
@param vnode_id The ID for this vnode.
|
||||
@param vnode_id_str A string representation of the above ID.
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t FetchDecision(
|
||||
const kauth_cred_t cred, const vnode_t vp, const uint64_t vnode_id, const char *vnode_id_str);
|
||||
const kauth_cred_t cred, const vnode_t vp, const uint64_t vnode_id);
|
||||
|
||||
/**
|
||||
Posts the requested message to the decision data queue.
|
||||
@@ -256,8 +256,8 @@ class SantaDecisionManager : public OSObject {
|
||||
|
||||
IOSharedDataQueue *decision_dataqueue_;
|
||||
IOSharedDataQueue *log_dataqueue_;
|
||||
int32_t failed_decision_queue_requests_;
|
||||
int32_t failed_log_queue_requests_;
|
||||
uint32_t failed_decision_queue_requests_;
|
||||
uint32_t failed_log_queue_requests_;
|
||||
|
||||
int32_t listener_invocations_;
|
||||
|
||||
@@ -265,6 +265,8 @@ class SantaDecisionManager : public OSObject {
|
||||
|
||||
kauth_listener_t vnode_listener_;
|
||||
kauth_listener_t fileop_listener_;
|
||||
|
||||
struct timespec ts_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -131,6 +131,7 @@ IOReturn SantaDriverClient::static_open(
|
||||
|
||||
IOReturn SantaDriverClient::allow_binary(const uint64_t vnode_id) {
|
||||
decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
|
||||
wakeup((void *)vnode_id);
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
@@ -147,6 +148,7 @@ IOReturn SantaDriverClient::static_allow_binary(
|
||||
|
||||
IOReturn SantaDriverClient::deny_binary(const uint64_t vnode_id) {
|
||||
decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
|
||||
wakeup((void *)vnode_id);
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,332 @@
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
// file info keys
|
||||
static NSString *const kPath = @"Path";
|
||||
static NSString *const kBundleName = @"Bundle Name";
|
||||
static NSString *const kBundleVersion = @"Bundle Version";
|
||||
static NSString *const kBundleVersionStr = @"Bundle Version Str";
|
||||
static NSString *const kDownloadReferrerURL = @"Download Referrer URL";
|
||||
static NSString *const kDownloadURL = @"Download URL";
|
||||
static NSString *const kDownloadTimestamp = @"Download Timestamp";
|
||||
static NSString *const kDownloadAgent = @"Download Agent";
|
||||
static NSString *const kType = @"Type";
|
||||
static NSString *const kPageZero = @"Page Zero";
|
||||
static NSString *const kCodeSigned = @"Code-signed";
|
||||
static NSString *const kRule = @"Rule";
|
||||
static NSString *const kSigningChain = @"Signing Chain";
|
||||
|
||||
// signing chain keys
|
||||
static NSString *const kCommonName = @"Common Name";
|
||||
static NSString *const kOrganization = @"Organization";
|
||||
static NSString *const kOrganizationalUnit = @"Organizational Unit";
|
||||
static NSString *const kValidFrom = @"Valid From";
|
||||
static NSString *const kValidUntil = @"Valid Until";
|
||||
|
||||
// shared file info & signing chain keys
|
||||
static NSString *const kSHA256 = @"SHA-256";
|
||||
static NSString *const kSHA1 = @"SHA-1";
|
||||
|
||||
#pragma mark SNTCommandFileInfo
|
||||
|
||||
@interface SNTCommandFileInfo : NSObject<SNTCommand>
|
||||
|
||||
@property(nonatomic) SNTXPCConnection *daemonConn;
|
||||
@property(nonatomic) SNTFileInfo *fileInfo;
|
||||
@property(nonatomic) MOLCodesignChecker *csc;
|
||||
|
||||
// file path used for object initialization
|
||||
@property(readonly, nonatomic) NSString *filePath;
|
||||
|
||||
// Block type to be used with propertyMap values
|
||||
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *);
|
||||
|
||||
// on read generated properties
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock path;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock sha256;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock sha1;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleName;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleVersion;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleShortVersionString;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadReferrerURL;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadURL;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadTimestamp;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadAgent;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock type;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock pageZero;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock rule;
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock signingChain;
|
||||
|
||||
// Mapping between property string keys and SNTAttributeBlocks
|
||||
@property(readonly, nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
|
||||
|
||||
// Common Date Formatter
|
||||
@property(nonatomic) NSDateFormatter *dateFormatter;
|
||||
|
||||
// CLI option
|
||||
@property(nonatomic) BOOL jsonOutput;
|
||||
|
||||
// Block Helpers
|
||||
- (NSString *)humanReadableFileType:(SNTFileInfo *)fi;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTCommandFileInfo
|
||||
|
||||
REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath
|
||||
daemonConnection:(SNTXPCConnection *)daemonConn
|
||||
jsonOutput:(BOOL)jsonOutput {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_daemonConn = daemonConn;
|
||||
_jsonOutput = jsonOutput;
|
||||
_dateFormatter = [[NSDateFormatter alloc] init];
|
||||
_dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
_propertyMap = @{ kPath : self.path,
|
||||
kSHA256 : self.sha256,
|
||||
kSHA1 : self.sha1,
|
||||
kBundleName : self.bundleName,
|
||||
kBundleVersion : self.bundleVersion,
|
||||
kBundleVersionStr : self.bundleVersionStr,
|
||||
kDownloadReferrerURL : self.downloadReferrerURL,
|
||||
kDownloadURL : self.downloadURL,
|
||||
kDownloadTimestamp : self.downloadTimestamp,
|
||||
kDownloadAgent : self.downloadAgent,
|
||||
kType : self.type,
|
||||
kPageZero : self.pageZero,
|
||||
kCodeSigned : self.codeSigned,
|
||||
kRule : self.rule,
|
||||
kSigningChain : self.signingChain };
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark property getters
|
||||
|
||||
- (SNTFileInfo *)fileInfo {
|
||||
if (!_fileInfo) {
|
||||
_fileInfo = [[SNTFileInfo alloc] initWithPath:self.filePath];
|
||||
if (!_fileInfo) {
|
||||
if (isatty(STDOUT_FILENO) && !self.jsonOutput) {
|
||||
printf("\rInvalid or empty file: %s\n", self.filePath.UTF8String);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _fileInfo;
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)path {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.path;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)sha256 {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.SHA256;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)sha1 {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.SHA1;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleName {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.bundleName;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleVersion {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.bundleVersion;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleVersionStr {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.bundleShortVersionString;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadReferrerURL {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.quarantineRefererURL;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadURL {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.quarantineDataURL;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadTimestamp {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return [fi.dateFormatter stringFromDate:fi.fileInfo.quarantineTimestamp];
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadAgent {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
return fi.fileInfo.quarantineAgentBundleID;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)type {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
NSArray *archs = [fi.fileInfo architectures];
|
||||
if (archs.count == 0) {
|
||||
return [fi humanReadableFileType:fi.fileInfo];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%@ (%@)",
|
||||
[fi humanReadableFileType:fi.fileInfo], [archs componentsJoinedByString:@", "]];
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)pageZero {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
if ([fi.fileInfo isMissingPageZero]) {
|
||||
return @"__PAGEZERO segment missing/bad!";
|
||||
}
|
||||
return nil;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)codeSigned {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:self.filePath error:&error];
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
return @"No";
|
||||
case errSecCSSignatureFailed:
|
||||
case errSecCSStaticCodeChanged:
|
||||
case errSecCSSignatureNotVerifiable:
|
||||
case errSecCSSignatureUnsupported:
|
||||
return @"Yes, but code/signature changed/unverifiable";
|
||||
case errSecCSResourceDirectoryFailed:
|
||||
case errSecCSResourceNotSupported:
|
||||
case errSecCSResourceRulesInvalid:
|
||||
case errSecCSResourcesInvalid:
|
||||
case errSecCSResourcesNotFound:
|
||||
case errSecCSResourcesNotSealed:
|
||||
return @"Yes, but resources invalid";
|
||||
case errSecCSReqFailed:
|
||||
case errSecCSReqInvalid:
|
||||
case errSecCSReqUnsupported:
|
||||
return @"Yes, but failed requirement validation";
|
||||
case errSecCSInfoPlistFailed:
|
||||
return @"Yes, but can't validate as Info.plist is missing";
|
||||
default: {
|
||||
return [NSString stringWithFormat:@"Yes, but failed to validate (%ld)", error.code];
|
||||
}
|
||||
}
|
||||
} else if (fi.csc.signatureFlags & kSecCodeSignatureAdhoc) {
|
||||
return @"Yes, but ad-hoc";
|
||||
} else {
|
||||
return @"Yes";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)rule {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
__block SNTRule *r;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[fi.daemonConn resume];
|
||||
});
|
||||
dispatch_group_enter(group);
|
||||
if (!fi.csc) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
|
||||
}
|
||||
NSString *leafCertSHA = [[fi.csc.certificates firstObject] SHA256];
|
||||
[[fi.daemonConn remoteObjectProxy] databaseRuleForBinarySHA256:fi.fileInfo.SHA256
|
||||
certificateSHA256:leafCertSHA
|
||||
reply:^(SNTRule *rule) {
|
||||
if (rule) r = rule;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
return @"Cannot communicate with daemon";
|
||||
} else {
|
||||
NSString *output;
|
||||
switch (r.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
output = @"Whitelisted";
|
||||
if (isatty(STDOUT_FILENO) && !fi.jsonOutput) {
|
||||
output = @"\033[32mWhitelisted\033[0m";
|
||||
}
|
||||
return output;
|
||||
break;
|
||||
case SNTRuleStateBlacklist:
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
output = @"Blacklisted";
|
||||
if (isatty(STDOUT_FILENO) && !fi.jsonOutput) {
|
||||
output = @"\033[31mBlacklisted\033[0m";
|
||||
}
|
||||
return output;
|
||||
break;
|
||||
default:
|
||||
output = @"None";
|
||||
if (isatty(STDOUT_FILENO) && !fi.jsonOutput) {
|
||||
output = @"\033[33mNone\033[0m";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)signingChain {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
if (!fi.csc) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
|
||||
}
|
||||
if (fi.csc.certificates.count) {
|
||||
NSMutableArray *certs = [[NSMutableArray alloc] initWithCapacity:fi.csc.certificates.count];
|
||||
[fi.csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c, unsigned long idx,
|
||||
BOOL *stop) {
|
||||
[certs addObject:@{ kSHA256 : c.SHA256 ?: @"null",
|
||||
kSHA1 : c.SHA1 ?: @"null",
|
||||
kCommonName : c.commonName ?: @"null",
|
||||
kOrganization : c.orgName ?: @"null",
|
||||
kOrganizationalUnit : c.orgUnit ?: @"null",
|
||||
kValidFrom : [fi.dateFormatter stringFromDate:c.validFrom] ?: @"null",
|
||||
kValidUntil : [fi.dateFormatter stringFromDate:c.validUntil]
|
||||
?: @"null"
|
||||
}];
|
||||
}];
|
||||
return certs;
|
||||
}
|
||||
return nil;
|
||||
};
|
||||
}
|
||||
|
||||
- (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
|
||||
if ([fi isScript]) return @"Script";
|
||||
if ([fi isExecutable]) return @"Executable";
|
||||
if ([fi isDylib]) return @"Dynamic Library";
|
||||
if ([fi isBundle]) return @"Bundle/Plugin";
|
||||
if ([fi isKext]) return @"Kernel Extension";
|
||||
if ([fi isXARArchive]) return @"XAR Archive";
|
||||
if ([fi isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
}
|
||||
|
||||
#pragma mark SNTCommand protocol methods
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
@@ -43,190 +362,244 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"The details provided will be the same ones Santa uses to make a decision\n"
|
||||
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
|
||||
@"the type of file.");
|
||||
return [NSString stringWithFormat:
|
||||
@"The details provided will be the same ones Santa uses to make a decision\n"
|
||||
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
|
||||
@"the type of file."
|
||||
@"\n"
|
||||
@"Usage: santactl fileinfo [options] [file-paths]\n"
|
||||
@" --json: output in json format\n"
|
||||
@" --key: search and return this one piece of information\n"
|
||||
@" valid Keys:\n"
|
||||
@"%@\n"
|
||||
@" valid keys when using --cert-index:\n"
|
||||
@"%@\n"
|
||||
@" --cert-index: an integer corresponding to a certificate of the signing chain\n"
|
||||
@" 1 for the leaf certificate\n"
|
||||
@" -1 for the root certificate\n"
|
||||
@" 2 and up for the intermediates / root\n"
|
||||
@"\n"
|
||||
@"Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo /usr/bin/yes /bin/*\n",
|
||||
[self printKeyArray:[self fileInfoKeys]],
|
||||
[self printKeyArray:[self signingChainKeys]]];
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
NSString *filePath = [arguments firstObject];
|
||||
#ifdef DEBUG
|
||||
NSDate *startTime = [NSDate date];
|
||||
#endif
|
||||
|
||||
if (!filePath) {
|
||||
printf("Missing file path\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!arguments.count) [self printErrorUsageAndExit:@"No arguments"];
|
||||
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
|
||||
if (!fileInfo) {
|
||||
printf("Invalid or empty file\n");
|
||||
exit(1);
|
||||
}
|
||||
BOOL jsonOutput = NO;
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
NSArray *filePaths;
|
||||
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
[self parseArguments:arguments
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
|
||||
if (isatty(STDOUT_FILENO)) printf("Hashing...");
|
||||
NSString *sha1, *sha256;
|
||||
[fileInfo hashSHA1:&sha1 SHA256:&sha256];
|
||||
if (isatty(STDOUT_FILENO)) printf("\r");
|
||||
__block NSMutableArray *outputHashes = [[NSMutableArray alloc] init];
|
||||
__block NSOperationQueue *hashQueue = [[NSOperationQueue alloc] init];
|
||||
hashQueue.maxConcurrentOperationCount = 15;
|
||||
__block NSUInteger hashed = 0;
|
||||
|
||||
[self printKey:@"Path" value:fileInfo.path];
|
||||
[self printKey:@"SHA-256" value:sha256];
|
||||
[self printKey:@"SHA-1" value:sha1];
|
||||
|
||||
if (fileInfo.bundlePath) {
|
||||
[self printKey:@"Bundle Name" value:fileInfo.bundleName];
|
||||
[self printKey:@"Bundle Version" value:fileInfo.bundleVersion];
|
||||
[self printKey:@"Bundle Version Str" value:fileInfo.bundleShortVersionString];
|
||||
}
|
||||
|
||||
if (fileInfo.quarantineDataURL) {
|
||||
[self printKey:@"Download Referer URL" value:fileInfo.quarantineRefererURL];
|
||||
[self printKey:@"Download URL" value:fileInfo.quarantineDataURL];
|
||||
[self printKey:@"Download Timestamp"
|
||||
value:[dateFormatter stringFromDate:fileInfo.quarantineTimestamp]];
|
||||
[self printKey:@"Download Agent" value:fileInfo.quarantineAgentBundleID];
|
||||
}
|
||||
|
||||
NSArray *archs = [fileInfo architectures];
|
||||
if (archs.count == 0) {
|
||||
[self printKey:@"Type" value:[self humanReadableFileType:fileInfo]];
|
||||
exit(0);
|
||||
}
|
||||
|
||||
NSString *s = [NSString stringWithFormat:@"%@ (%@)",
|
||||
[self humanReadableFileType:fileInfo],
|
||||
[archs componentsJoinedByString:@", "]];
|
||||
[self printKey:@"Type" value:s];
|
||||
|
||||
if ([fileInfo isMissingPageZero]) {
|
||||
[self printKey:@"Page Zero" value:@"__PAGEZERO segment missing/bad!"];
|
||||
}
|
||||
|
||||
// Code signature state
|
||||
NSError *error;
|
||||
MOLCodesignChecker *csc = [[MOLCodesignChecker alloc] initWithBinaryPath:filePath error:&error];
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
[self printKey:@"Code-signed" value:@"No"];
|
||||
break;
|
||||
case errSecCSSignatureFailed:
|
||||
case errSecCSStaticCodeChanged:
|
||||
case errSecCSSignatureNotVerifiable:
|
||||
case errSecCSSignatureUnsupported:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but code/signature changed/unverifiable"];
|
||||
break;
|
||||
case errSecCSResourceDirectoryFailed:
|
||||
case errSecCSResourceNotSupported:
|
||||
case errSecCSResourceRulesInvalid:
|
||||
case errSecCSResourcesInvalid:
|
||||
case errSecCSResourcesNotFound:
|
||||
case errSecCSResourcesNotSealed:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but resources invalid"];
|
||||
break;
|
||||
case errSecCSReqFailed:
|
||||
case errSecCSReqInvalid:
|
||||
case errSecCSReqUnsupported:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but failed requirement validation"];
|
||||
break;
|
||||
case errSecCSInfoPlistFailed:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but can't validate as Info.plist is missing"];
|
||||
break;
|
||||
default: {
|
||||
NSString *val = [NSString stringWithFormat:@"Yes, but failed to validate (%ld)",
|
||||
error.code];
|
||||
[self printKey:@"Code-signed" value:val];
|
||||
break;
|
||||
[filePaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
NSBlockOperation *hashOperation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
if (isatty(STDOUT_FILENO) && !jsonOutput) {
|
||||
printf("\rCalculating %lu/%lu", ++hashed, filePaths.count);
|
||||
}
|
||||
}
|
||||
} else if (csc.signatureFlags & kSecCodeSignatureAdhoc) {
|
||||
[self printKey:@"Code-signed" value:@"Yes, but ad-hoc"];
|
||||
} else {
|
||||
[self printKey:@"Code-signed" value:@"Yes"];
|
||||
}
|
||||
|
||||
// Binary rule state
|
||||
__block SNTRule *r;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
[daemonConn resume];
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] databaseBinaryRuleForSHA256:sha256 reply:^(SNTRule *rule) {
|
||||
if (rule) r = rule;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
NSString *leafCertSHA = [[csc.certificates firstObject] SHA256];
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] databaseCertificateRuleForSHA256:leafCertSHA
|
||||
reply:^(SNTRule *rule) {
|
||||
if (!r && rule) r = rule;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
[self printKey:@"Rule" value:@"Cannot communicate with daemon"];
|
||||
} else {
|
||||
NSString *output;
|
||||
switch (r.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
output = @"Whitelisted";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[32mWhitelisted\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
break;
|
||||
case SNTRuleStateBlacklist:
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
output = @"Blacklisted";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[31mBlacklisted\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
break;
|
||||
default:
|
||||
output = @"None";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[33mNone\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
}
|
||||
}
|
||||
SNTCommandFileInfo *fi = [[self alloc] initWithFilePath:obj
|
||||
daemonConnection:daemonConn
|
||||
jsonOutput:jsonOutput];
|
||||
if (!fi.fileInfo) return;
|
||||
|
||||
// Signing chain
|
||||
if (csc.certificates.count) {
|
||||
printf("Signing chain:\n");
|
||||
__block NSMutableDictionary *outputHash = [[NSMutableDictionary alloc] init];
|
||||
|
||||
[csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c,
|
||||
unsigned long idx,
|
||||
BOOL *stop) {
|
||||
printf(" %2lu. %-20s: %s\n", idx + 1, "SHA-256", [c.SHA256 UTF8String]);
|
||||
printf(" %-20s: %s\n", "SHA-1", [c.SHA1 UTF8String]);
|
||||
printf(" %-20s: %s\n", "Common Name", [c.commonName UTF8String]);
|
||||
printf(" %-20s: %s\n", "Organization", [c.orgName UTF8String]);
|
||||
printf(" %-20s: %s\n", "Organizational Unit", [c.orgUnit UTF8String]);
|
||||
printf(" %-20s: %s\n", "Valid From",
|
||||
[[dateFormatter stringFromDate:c.validFrom] UTF8String]);
|
||||
printf(" %-20s: %s\n", "Valid Until",
|
||||
[[dateFormatter stringFromDate:c.validUntil] UTF8String]);
|
||||
printf("\n");
|
||||
if (key && !certIndex) {
|
||||
SNTAttributeBlock block = fi.propertyMap[key];
|
||||
outputHash[key] = block(fi);
|
||||
} else if (certIndex) {
|
||||
NSArray *signingChain = fi.signingChain(fi);
|
||||
if (key) {
|
||||
if ([certIndex isEqual:@(-1)]) {
|
||||
outputHash[key] = signingChain.lastObject[key];
|
||||
} else {
|
||||
if (certIndex.unsignedIntegerValue - 1 < signingChain.count) {
|
||||
outputHash[key] = signingChain[certIndex.unsignedIntegerValue - 1][key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ([certIndex isEqual:@(-1)]) {
|
||||
outputHash[kSigningChain] = @[ signingChain.lastObject ?: @{} ];
|
||||
} else {
|
||||
NSMutableArray *indexedCert = [NSMutableArray arrayWithCapacity:signingChain.count];
|
||||
[signingChain enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (certIndex.unsignedIntegerValue - 1 == idx) {
|
||||
[indexedCert addObject:obj];
|
||||
} else {
|
||||
[indexedCert addObject:[NSNull null]];
|
||||
}
|
||||
}];
|
||||
if (indexedCert.count) outputHash[kSigningChain] = indexedCert;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
[fi.propertyMap enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
SNTAttributeBlock block = fi.propertyMap[key];
|
||||
outputHash[key] = block(fi);
|
||||
}];
|
||||
}
|
||||
if (outputHash.count) [outputHashes addObject:outputHash];
|
||||
}];
|
||||
hashOperation.qualityOfService = NSQualityOfServiceUserInitiated;
|
||||
[hashQueue addOperation:hashOperation];
|
||||
}];
|
||||
[hashQueue waitUntilAllOperationsAreFinished];
|
||||
printf("\33[2K\r");
|
||||
if (outputHashes.count) [self printOutputHashes:outputHashes jsonOutput:jsonOutput];
|
||||
|
||||
#ifdef DEBUG
|
||||
if (isatty(STDOUT_FILENO) && !jsonOutput) {
|
||||
printf("Calculating time: %f\n", [[NSDate date] timeIntervalSinceDate:startTime]);
|
||||
}
|
||||
#endif
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
+ (void)printKey:(NSString *)key value:(NSString *)value {
|
||||
if (!key || !value) return;
|
||||
#pragma mark FileInfo helper methods
|
||||
|
||||
+ (NSArray *)fileInfoKeys {
|
||||
return @[ kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr,
|
||||
kDownloadReferrerURL, kDownloadURL, kDownloadTimestamp, kDownloadAgent,
|
||||
kType, kPageZero, kCodeSigned, kRule, kSigningChain ];
|
||||
}
|
||||
|
||||
+ (NSArray *)signingChainKeys {
|
||||
return @[ kSHA256, kSHA1, kCommonName, kOrganization, kOrganizationalUnit, kValidFrom,
|
||||
kValidUntil ];
|
||||
}
|
||||
|
||||
+ (NSString *)printKeyArray:(NSArray *)array {
|
||||
__block NSMutableString *string = [[NSMutableString alloc] init];
|
||||
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
[string appendString:[NSString stringWithFormat:@" \"%@\"\n", obj]];
|
||||
}];
|
||||
return string;
|
||||
}
|
||||
|
||||
+ (void)printErrorUsageAndExit:(NSString *)error {
|
||||
printf("%s\n\n", [error UTF8String]);
|
||||
printf("%s\n", [[self longHelpText] UTF8String]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
+ (void)parseArguments:(NSArray *)args
|
||||
forKey:(NSString **)key
|
||||
certIndex:(NSNumber **)certIndex
|
||||
jsonOutput:(BOOL *)jsonOutput
|
||||
filePaths:(NSArray **)filePaths {
|
||||
__block NSMutableArray *paths = [[NSMutableArray alloc] init];
|
||||
[args enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([obj caseInsensitiveCompare:@"--json"] == NSOrderedSame) {
|
||||
*jsonOutput = YES;
|
||||
} else if ([obj caseInsensitiveCompare:@"--cert-index"] == NSOrderedSame) {
|
||||
if (++idx > args.count - 1 || [args[idx] hasPrefix:@"--"]) {
|
||||
[self printErrorUsageAndExit:@"\n--cert-index requires an argument"];
|
||||
}
|
||||
*certIndex = @([args[idx] integerValue]);
|
||||
} else if ([obj caseInsensitiveCompare:@"--key"] == NSOrderedSame) {
|
||||
if (++idx > args.count - 1 || [args[idx] hasPrefix:@"--"]) {
|
||||
[self printErrorUsageAndExit:@"\n--key requires an argument"];
|
||||
}
|
||||
*key = args[idx];
|
||||
} else if ([@([obj integerValue]) isEqual:*certIndex] || [obj isEqual:*key]) {
|
||||
return;
|
||||
} else {
|
||||
[paths addObject:args[idx]];
|
||||
}
|
||||
}];
|
||||
if (*key && !*certIndex && ![self.fileInfoKeys containsObject:*key]) {
|
||||
[self printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid key", *key]];
|
||||
} else if (*key && *certIndex && ![self.signingChainKeys containsObject:*key]) {
|
||||
[self printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid key when using --cert-index", *key]];
|
||||
} else if ([@(0) isEqual:*certIndex]) {
|
||||
[self printErrorUsageAndExit:@"\n0 is an invalid --cert-index\n --cert-index is 1 indexed"];
|
||||
}
|
||||
if (!paths.count) [self printErrorUsageAndExit:@"\nat least one file-path is needed"];
|
||||
*filePaths = paths.copy;
|
||||
}
|
||||
|
||||
+ (void)printOutputHashes:(NSArray *)outputHashes jsonOutput:(BOOL)jsonOutput {
|
||||
if (jsonOutput) {
|
||||
id object = (outputHashes.count > 1) ? outputHashes : outputHashes.firstObject;
|
||||
if (!object) return;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:NULL];
|
||||
printf("%s\n", [[NSString alloc] initWithData:jsonData
|
||||
encoding:NSUTF8StringEncoding].UTF8String);
|
||||
return;
|
||||
}
|
||||
|
||||
[outputHashes enumerateObjectsUsingBlock:^(id outputHash, NSUInteger idx, BOOL *stop) {
|
||||
if ([outputHash count] == 1) {
|
||||
return [self printValueFromOutputHash:outputHash];
|
||||
}
|
||||
[self.fileInfoKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
|
||||
[self printValueForKey:key fromOutputHash:outputHash];
|
||||
}];
|
||||
printf("\n");
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)printValueForKey:(NSString *)key fromOutputHash:(NSDictionary *)outputHash {
|
||||
id value = outputHash[key];
|
||||
if (!value) return;
|
||||
if ([key isEqualToString:kSigningChain]) {
|
||||
return [self printSigningChain:value];
|
||||
}
|
||||
printf("%-21s: %s\n", [key UTF8String], [value UTF8String]);
|
||||
}
|
||||
|
||||
+ (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
|
||||
if ([fi isScript]) return @"Script";
|
||||
if ([fi isExecutable]) return @"Executable";
|
||||
if ([fi isDylib]) return @"Dynamic Library";
|
||||
if ([fi isKext]) return @"Kernel Extension";
|
||||
if ([fi isXARArchive]) return @"XAR Archive";
|
||||
if ([fi isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
+ (void)printValueFromOutputHash:(NSDictionary *)outputHash {
|
||||
if ([[[outputHash allKeys] firstObject] isEqualToString:kSigningChain]) {
|
||||
return [self printSigningChain:[[outputHash allValues] firstObject]];
|
||||
}
|
||||
printf("%s\n", [[[outputHash allValues] firstObject] UTF8String]);
|
||||
}
|
||||
|
||||
+ (void)printSigningChain:(NSArray *)signingChain {
|
||||
if (!signingChain) return;
|
||||
printf("%s:\n", kSigningChain.UTF8String);
|
||||
__block int i = 0;
|
||||
[signingChain enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([obj isEqual:[NSNull null]]) return;
|
||||
if (i++) printf("\n");
|
||||
printf(" %2lu. %-20s: %s\n", idx + 1, kSHA256.UTF8String,
|
||||
((NSString *)obj[kSHA256]).UTF8String);
|
||||
printf(" %-20s: %s\n", kSHA1.UTF8String,
|
||||
((NSString *)obj[kSHA1]).UTF8String);
|
||||
printf(" %-20s: %s\n", kCommonName.UTF8String,
|
||||
((NSString *)obj[kCommonName]).UTF8String);
|
||||
printf(" %-20s: %s\n", kOrganization.UTF8String,
|
||||
((NSString *)obj[kOrganization]).UTF8String);
|
||||
printf(" %-20s: %s\n", kOrganizationalUnit.UTF8String,
|
||||
((NSString *)obj[kOrganizationalUnit]).UTF8String);
|
||||
printf(" %-20s: %s\n", kValidFrom.UTF8String,
|
||||
((NSString *)obj[kValidFrom]).UTF8String);
|
||||
printf(" %-20s: %s\n", kValidUntil.UTF8String,
|
||||
((NSString *)obj[kValidUntil]).UTF8String);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -40,13 +40,27 @@
|
||||
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
requestDict[kPrimaryUser] = self.syncState.machineOwner;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
|
||||
requestDict[kBinaryRuleCount] = @(binary);
|
||||
requestDict[kCertificateRuleCount] = @(certificate);
|
||||
dispatch_semaphore_signal(sema);
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case SNTClientModeMonitor:
|
||||
requestDict[kClientMode] = kClientModeMonitor; break;
|
||||
case SNTClientModeLockdown:
|
||||
requestDict[kClientMode] = kClientModeLockdown; break;
|
||||
default: break;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
|
||||
|
||||
// If user requested it or we've never had a successful sync, try from a clean slate.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] ||
|
||||
|
||||
@@ -39,14 +39,11 @@
|
||||
- (NSUInteger)certificateRuleCount;
|
||||
|
||||
///
|
||||
/// @return Rule for binary with given SHA-256
|
||||
/// @return Rule for binary or certificate with given SHA-256. The binary rule will be returned
|
||||
/// if it exists. If not, the certificate rule will be returned if it exists.
|
||||
///
|
||||
- (SNTRule *)binaryRuleForSHA256:(NSString *)SHA256;
|
||||
|
||||
///
|
||||
/// @return Rule for certificate with given SHA-256
|
||||
///
|
||||
- (SNTRule *)certificateRuleForSHA256:(NSString *)SHA256;
|
||||
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256;
|
||||
|
||||
///
|
||||
/// Add an array of rules to the database. The rules will be added within a transaction and the
|
||||
|
||||
@@ -108,25 +108,15 @@
|
||||
return rule;
|
||||
}
|
||||
|
||||
- (SNTRule *)certificateRuleForSHA256:(NSString *)SHA256 {
|
||||
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256 {
|
||||
__block SNTRule *rule;
|
||||
|
||||
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
FMResultSet *rs = [db executeQuery:@"SELECT * FROM certrules WHERE shasum=? LIMIT 1", SHA256];
|
||||
if ([rs next]) {
|
||||
rule = [self ruleFromResultSet:rs];
|
||||
}
|
||||
[rs close];
|
||||
}];
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
- (SNTRule *)binaryRuleForSHA256:(NSString *)SHA256 {
|
||||
__block SNTRule *rule;
|
||||
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
FMResultSet *rs = [db executeQuery:@"SELECT * FROM binrules WHERE shasum=? LIMIT 1", SHA256];
|
||||
FMResultSet *rs =
|
||||
[db executeQuery:
|
||||
@"SELECT * FROM rules WHERE (shasum=? and type=1) OR (shasum=? AND type=2) LIMIT 1",
|
||||
binarySHA256, certificateSHA256];
|
||||
if ([rs next]) {
|
||||
rule = [self ruleFromResultSet:rs];
|
||||
}
|
||||
|
||||
@@ -135,26 +135,27 @@
|
||||
- (void)beginListeningForDecisionRequests {
|
||||
dispatch_queue_t exec_queue = dispatch_queue_create(
|
||||
"com.google.santad.execution_queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_set_target_queue(exec_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||
dispatch_set_target_queue(
|
||||
exec_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||
|
||||
[self.driverManager listenForDecisionRequests:^(santa_message_t message) {
|
||||
@autoreleasepool {
|
||||
switch (message.action) {
|
||||
case ACTION_REQUEST_SHUTDOWN: {
|
||||
LOGI(@"Driver requested a shutdown");
|
||||
exit(0);
|
||||
}
|
||||
case ACTION_REQUEST_BINARY: {
|
||||
dispatch_async(exec_queue, ^{
|
||||
dispatch_async(exec_queue, ^{
|
||||
switch (message.action) {
|
||||
case ACTION_REQUEST_SHUTDOWN: {
|
||||
LOGI(@"Driver requested a shutdown");
|
||||
exit(0);
|
||||
}
|
||||
case ACTION_REQUEST_BINARY: {
|
||||
[_execController validateBinaryWithMessage:message];
|
||||
});
|
||||
break;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOGE(@"Received decision request without a valid action: %d", message.action);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
LOGE(@"Received decision request without a valid action: %d", message.action);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -165,33 +166,36 @@
|
||||
dispatch_set_target_queue(
|
||||
log_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
|
||||
// Limit number of threads the queue can create.
|
||||
dispatch_semaphore_t concurrencyLimiter = dispatch_semaphore_create(15);
|
||||
|
||||
[self.driverManager listenForLogRequests:^(santa_message_t message) {
|
||||
@autoreleasepool {
|
||||
switch (message.action) {
|
||||
case ACTION_NOTIFY_DELETE:
|
||||
case ACTION_NOTIFY_EXCHANGE:
|
||||
case ACTION_NOTIFY_LINK:
|
||||
case ACTION_NOTIFY_RENAME:
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
dispatch_async(log_queue, ^{
|
||||
dispatch_semaphore_wait(concurrencyLimiter, DISPATCH_TIME_FOREVER);
|
||||
dispatch_async(log_queue, ^{
|
||||
switch (message.action) {
|
||||
case ACTION_NOTIFY_DELETE:
|
||||
case ACTION_NOTIFY_EXCHANGE:
|
||||
case ACTION_NOTIFY_LINK:
|
||||
case ACTION_NOTIFY_RENAME:
|
||||
case ACTION_NOTIFY_WRITE: {
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] fileChangesRegex];
|
||||
NSString *path = @(message.path);
|
||||
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
|
||||
[_eventLog logFileModification:message];
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXEC: {
|
||||
dispatch_async(log_queue, ^{
|
||||
break;
|
||||
}
|
||||
case ACTION_NOTIFY_EXEC: {
|
||||
[_eventLog logAllowedExecution:message];
|
||||
});
|
||||
break;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOGE(@"Received log request without a valid action: %d", message.action);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOGE(@"Received log request without a valid action: %d", message.action);
|
||||
break;
|
||||
}
|
||||
dispatch_semaphore_signal(concurrencyLimiter);
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -134,12 +134,11 @@ double watchdogRAMPeak = 0;
|
||||
[[SNTDatabaseController eventTable] deleteEventsWithIds:ids];
|
||||
}
|
||||
|
||||
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] binaryRuleForSHA256:sha256]);
|
||||
}
|
||||
|
||||
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] certificateRuleForSHA256:sha256]);
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] ruleForBinarySHA256:binarySHA256
|
||||
certificateSHA256:certificateSHA256]);
|
||||
}
|
||||
|
||||
#pragma mark Config Ops
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#import "MOLCertificate.h"
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
#import "SNTLogging.h"
|
||||
@@ -29,6 +30,13 @@
|
||||
@interface SNTEventLog ()
|
||||
@property NSMutableDictionary *detailStore;
|
||||
@property dispatch_queue_t detailStoreQueue;
|
||||
|
||||
// Caches for uid->username and gid->groupname lookups.
|
||||
// Both dictionaries must be accessed from the nameMapQueue
|
||||
// to enforce thread-safety.
|
||||
@property NSMutableDictionary *userNameMap;
|
||||
@property NSMutableDictionary *groupNameMap;
|
||||
@property dispatch_queue_t nameMapQueue;
|
||||
@end
|
||||
|
||||
@implementation SNTEventLog
|
||||
@@ -39,6 +47,11 @@
|
||||
_detailStore = [NSMutableDictionary dictionaryWithCapacity:10000];
|
||||
_detailStoreQueue = dispatch_queue_create("com.google.santad.detail_store",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_userNameMap = [NSMutableDictionary dictionary];
|
||||
_groupNameMap = [NSMutableDictionary dictionary];
|
||||
_nameMapQueue = dispatch_queue_create("com.google.santad.name_map_queue",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -90,16 +103,10 @@
|
||||
char ppath[PATH_MAX] = "(null)";
|
||||
proc_pidpath(message.pid, ppath, PATH_MAX);
|
||||
|
||||
const char *user = "";
|
||||
const char *group = "";
|
||||
struct passwd *pw = getpwuid(message.uid);
|
||||
if (pw) user = pw->pw_name;
|
||||
struct group *gr = getgrgid(message.gid);
|
||||
if (gr) group = gr->gr_name;
|
||||
|
||||
[outStr appendFormat:@"|pid=%d|ppid=%d|process=%s|processpath=%s|uid=%d|user=%s|gid=%d|group=%s",
|
||||
[outStr appendFormat:@"|pid=%d|ppid=%d|process=%s|processpath=%s|uid=%d|user=%@|gid=%d|group=%@",
|
||||
message.pid, message.ppid, message.pname, ppath,
|
||||
message.uid, user, message.gid, group];
|
||||
message.uid, [self nameForUID:message.uid],
|
||||
message.gid, [self nameForGID:message.gid]];
|
||||
LOGI(@"%@", outStr);
|
||||
}
|
||||
|
||||
@@ -127,7 +134,7 @@
|
||||
break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
d = @"ALLOW";
|
||||
r = @"CERTIFICATE";
|
||||
r = @"CERT";
|
||||
logArgs = YES;
|
||||
break;
|
||||
case SNTEventStateAllowScope:
|
||||
@@ -186,15 +193,21 @@
|
||||
[outLog appendFormat:@"|quarantine_url=%@", [self sanitizeString:cd.quarantineURL]];
|
||||
}
|
||||
|
||||
NSString *user, *group;
|
||||
struct passwd *pw = getpwuid(message.uid);
|
||||
if (pw) user = @(pw->pw_name);
|
||||
struct group *gr = getgrgid(message.gid);
|
||||
if (gr) group = @(gr->gr_name);
|
||||
NSString *mode;
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor:
|
||||
mode = @"M"; break;
|
||||
case SNTClientModeLockdown:
|
||||
mode = @"L"; break;
|
||||
default:
|
||||
mode = @"U"; break;
|
||||
}
|
||||
|
||||
[outLog appendFormat:@"|pid=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@",
|
||||
message.pid, message.ppid, message.uid, user,
|
||||
message.gid, group];
|
||||
[outLog appendFormat:@"|pid=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@|mode=%@",
|
||||
message.pid, message.ppid,
|
||||
message.uid, [self nameForUID:message.uid],
|
||||
message.gid, [self nameForGID:message.gid],
|
||||
mode];
|
||||
|
||||
LOGI(@"%@", outLog);
|
||||
}
|
||||
@@ -396,6 +409,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)nameForUID:(uid_t)uid {
|
||||
__block NSString *name;
|
||||
|
||||
NSNumber *uidNumber = @(uid);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
name = self.userNameMap[uidNumber];
|
||||
});
|
||||
if (name) return name;
|
||||
|
||||
struct passwd *pw = getpwuid(uid);
|
||||
if (pw) {
|
||||
name = @(pw->pw_name);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
self.userNameMap[uidNumber] = name;
|
||||
});
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
- (NSString *)nameForGID:(gid_t)gid {
|
||||
__block NSString *name;
|
||||
|
||||
NSNumber *gidNumber = @(gid);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
name = self.groupNameMap[gidNumber];
|
||||
});
|
||||
if (name) return name;
|
||||
|
||||
struct group *gr = getgrgid(gid);
|
||||
if (gr) {
|
||||
name = @(gr->gr_name);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
self.groupNameMap[gidNumber] = name;
|
||||
});
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
Given an IOKit device path (like those provided by DiskArbitration), find the disk
|
||||
image path by looking up the device in the IOKit registry and getting its properties.
|
||||
|
||||
@@ -77,31 +77,35 @@
|
||||
#pragma mark Binary Validation
|
||||
|
||||
- (SNTEventState)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
|
||||
SNTRule *rule = [_ruleTable binaryRuleForSHA256:cd.sha256];
|
||||
SNTRule *rule = [_ruleTable ruleForBinarySHA256:cd.sha256 certificateSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowBinary;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockBinary;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
rule = [_ruleTable certificateRuleForSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowCertificate;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockCertificate;
|
||||
default: break;
|
||||
switch (rule.type) {
|
||||
case SNTRuleTypeBinary:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowBinary;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockBinary;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case SNTRuleTypeCertificate:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowCertificate;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockCertificate;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,14 +218,15 @@
|
||||
// Let the user know what happened, both on the terminal and in the GUI.
|
||||
NSAttributedString *s = [SNTBlockMessage attributedBlockMessageForEvent:se
|
||||
customMessage:cd.customMsg];
|
||||
NSString *msg = [NSString stringWithFormat:@"\033[1mSanta\033[0m\n\n%@\n\n", s.string];
|
||||
msg = [msg stringByAppendingFormat:@"\033[1mPath:\033[0m %@\n"
|
||||
@"\033[1mIdentifier:\033[0m %@\n"
|
||||
@"\033[1mParent:\033[0m %@ (%@)\n\n",
|
||||
se.filePath, se.fileSHA256, se.parentName, se.ppid];
|
||||
NSMutableString *msg = [NSMutableString stringWithCapacity:1024];
|
||||
[msg appendFormat:@"\n\033[1mSanta\033[0m\n\n%@\n\n", s.string];
|
||||
[msg appendFormat:@"\033[1mPath: \033[0m %@\n"
|
||||
@"\033[1mIdentifier:\033[0m %@\n"
|
||||
@"\033[1mParent: \033[0m %@ (%@)\n\n",
|
||||
se.filePath, se.fileSHA256, se.parentName, se.ppid];
|
||||
NSURL *detailURL = [SNTBlockMessage eventDetailURLForEvent:se];
|
||||
if (detailURL) {
|
||||
msg = [msg stringByAppendingFormat:@"%@\n\n", detailURL.absoluteString];
|
||||
[msg appendFormat:@"%@\n\n", detailURL.absoluteString];
|
||||
}
|
||||
[self printMessage:msg toTTYForPID:message.ppid];
|
||||
|
||||
@@ -311,6 +316,13 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
/**
|
||||
This runs `santactl sync` for the event that was just saved, so that the user
|
||||
has something to vote in straight away.
|
||||
|
||||
This method is always called on a serial queue to ensure the backoff works properly
|
||||
and to keep this low-priority method away from the high-priority decision making threads.
|
||||
*/
|
||||
- (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.
|
||||
@@ -365,44 +377,38 @@
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *devPath = [NSString stringWithFormat:@"/dev/%s", devname(taskInfo.e_tdev, S_IFCHR)];
|
||||
int fd = open(devPath.UTF8String, O_WRONLY | O_NOCTTY);
|
||||
@try {
|
||||
NSFileHandle *fh = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
|
||||
[fh writeData:[msg dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
} @catch (NSException *) { /* do nothing */ }
|
||||
// 16-bytes here is for future-proofing. Currently kern.tty.ptmx_max is
|
||||
// limited to 999 so 12 bytes should be enough.
|
||||
char devPath[16] = "/dev/";
|
||||
snprintf(devPath, 16, "/dev/%s", devname(taskInfo.e_tdev, S_IFCHR));
|
||||
int fd = open(devPath, O_WRONLY | O_NOCTTY);
|
||||
write(fd, msg.UTF8String, msg.length);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
|
||||
NSMutableDictionary *loggedInUsers = [[NSMutableDictionary alloc] init];
|
||||
NSMutableDictionary *loggedInHosts = [[NSMutableDictionary alloc] init];
|
||||
NSMutableDictionary *loggedInUsers = [NSMutableDictionary dictionary];
|
||||
NSMutableArray *loggedInHosts = [NSMutableArray array];
|
||||
|
||||
struct utmpx *nxt;
|
||||
while ((nxt = getutxent())) {
|
||||
if (nxt->ut_type != USER_PROCESS) continue;
|
||||
|
||||
NSString *userName = @(nxt->ut_user);
|
||||
|
||||
NSString *sessionName;
|
||||
if (strnlen(nxt->ut_host, 1) > 0) {
|
||||
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_host];
|
||||
sessionName = [NSString stringWithFormat:@"%@@%s", userName, nxt->ut_host];
|
||||
} else {
|
||||
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_line];
|
||||
sessionName = [NSString stringWithFormat:@"%@@%s", userName, nxt->ut_line];
|
||||
}
|
||||
|
||||
if (userName.length > 0) {
|
||||
loggedInUsers[userName] = [NSNull null];
|
||||
}
|
||||
|
||||
if (sessionName.length > 1) {
|
||||
loggedInHosts[sessionName] = [NSNull null];
|
||||
}
|
||||
if (userName.length) loggedInUsers[userName] = [NSNull null];
|
||||
if (sessionName.length) [loggedInHosts addObject:sessionName];
|
||||
}
|
||||
|
||||
endutxent();
|
||||
|
||||
*users = [loggedInUsers allKeys];
|
||||
*sessions = [loggedInHosts allKeys];
|
||||
*sessions = [loggedInHosts copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateWhitelist;
|
||||
OCMStub([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeBinary;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -109,7 +110,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateBlacklist;
|
||||
OCMStub([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeBinary;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -119,7 +121,6 @@
|
||||
|
||||
- (void)testCertificateWhitelistRule {
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
id cert = OCMClassMock([MOLCertificate class]);
|
||||
OCMStub([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
@@ -127,7 +128,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateWhitelist;
|
||||
OCMStub([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeCertificate;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
@@ -137,7 +139,6 @@
|
||||
|
||||
- (void)testCertificateBlacklistRule {
|
||||
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
|
||||
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
|
||||
|
||||
id cert = OCMClassMock([MOLCertificate class]);
|
||||
OCMStub([self.mockCodesignChecker leafCertificate]).andReturn(cert);
|
||||
@@ -145,7 +146,8 @@
|
||||
|
||||
SNTRule *rule = [[SNTRule alloc] init];
|
||||
rule.state = SNTRuleStateBlacklist;
|
||||
OCMStub([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
|
||||
rule.type = SNTRuleTypeCertificate;
|
||||
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
|
||||
|
||||
[self.sut validateBinaryWithMessage:[self getMessage]];
|
||||
|
||||
|
||||
@@ -137,12 +137,12 @@
|
||||
cleanSlate:NO
|
||||
error:nil];
|
||||
|
||||
SNTRule *r = [self.sut binaryRuleForSHA256:@"a"];
|
||||
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.shasum, @"a");
|
||||
XCTAssertEqual(r.type, SNTRuleTypeBinary);
|
||||
|
||||
r = [self.sut binaryRuleForSHA256:@"b"];
|
||||
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil];
|
||||
XCTAssertNil(r);
|
||||
}
|
||||
|
||||
@@ -151,12 +151,12 @@
|
||||
cleanSlate:NO
|
||||
error:nil];
|
||||
|
||||
SNTRule *r = [self.sut certificateRuleForSHA256:@"b"];
|
||||
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b"];
|
||||
XCTAssertNotNil(r);
|
||||
XCTAssertEqualObjects(r.shasum, @"b");
|
||||
XCTAssertEqual(r.type, SNTRuleTypeCertificate);
|
||||
|
||||
r = [self.sut certificateRuleForSHA256:@"a"];
|
||||
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a"];
|
||||
XCTAssertNil(r);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user