Compare commits

...

29 Commits

Author SHA1 Message Date
Tom Burgin
f631f219b0 santactl/sync: fixed exception when file_name is None / NSNull (#180)
* santactl/sync: fixed exception when file_name is None / NSNull

* review updates
2017-07-06 11:52:49 -04:00
Tom Burgin
aacae020b8 logs: add DAAppearanceTime to the DISKAPPEAR logs (#179)
* logs: add DAAppearanceTime to the DISKAPPEAR logs

* review updates

* discussion updates
2017-07-02 16:27:40 -04:00
Tom Burgin
7c426e0eec santactl/sync: upload file bundle executable relative path for bundle events (#178) 2017-06-28 11:55:21 -04:00
Tom Burgin
363826502f santabs: de-dupe generated events before upload (#177)
* santabs: de-dupe generated events before upload and remove locks

* review updates

* error updates
2017-06-22 17:46:04 -04:00
Russell Hancox
1cfadae068 SantaGUI: Don't show pop-up notifications for empty filenames (#176) 2017-06-12 11:28:32 -07:00
Tom Burgin
d3b3d722b4 santabs: use the ancestor bundle when searching for binaries (#175)
* santabs: use the ancestor bundle when searching for binaries

* review updates

* bundle tests
2017-06-09 17:04:23 -04:00
Tom Burgin
a82428958b santactl/rule: Add the ability to check the status of arbitrary sha256 hashes without on-disk artifacts. (#172) 2017-05-30 13:07:47 -04:00
Tom Burgin
b185632bda santad/SantaGUI: Add needsBundleHash property to SNTStoredEvent && (#170)
santactl/sync:sync server enables/disables client bundle support
2017-05-30 13:04:08 -04:00
Tom Burgin
e7a0c3d25b santactl/sync: Sync Server to set FCM interval and deadline (#168)
* santactl/sync: Sync Server to set FCM interval and deadline

* rename default constants

* review updates
2017-05-22 11:50:37 -04:00
Tom Burgin
ab33de2c15 project/config: Move /var/log/santa.log to /var/db/santa/santa.log (#173) 2017-05-22 11:10:10 -04:00
Tom Burgin
a1031cdc27 protect wakeup() from being called with 0 (#167)
* don't call wakeup on 0

* project: "share" the santabs target

* Project: Update CocoaPods to 1.2.1

* Project: pod deintegrate - pod install
2017-04-14 16:13:45 -04:00
Tom Burgin
e3ab3ca506 Update SNTCommandSyncEventUpload.m (#162) 2017-04-13 16:58:25 -04:00
Matthew Suozzo
b4cd1ccbee santa-driver: Fix a typo 2017-04-13 14:27:05 -04:00
Matthew Suozzo
14573a5714 santa-driver: Refactor cache expiration calculation 2017-04-12 22:35:08 -04:00
Tom Burgin
96150a9668 Bundle Events (#145)
* santabs: Create Santa Bundle Service

* common: SNTXPCConnection add initClientWithServiceName:

* santad: add logic for blocked bundles

* SantaGUI: add ui elements and xpc connections to / from santabs

* santactl/sync: add api features for syncing bundle events

* santactl/bundleinfo: add bundleinfo command for debug builds

* common: prefer bundle hash over file hash for event urls

* common: remove syncBackoff property - this is now handled in santactl sync

* common: add properties to support the bundle event api

* common: find a bundle from a nested binary

* review updates

* sane bundle hash time outs

* post rebase updates

* post review updates
2017-04-07 15:31:56 -04:00
Russell Hancox
c10c1303ed SantaGUI: Add preprocessor flag to import Cocoa for SNTBlockMessage 2017-04-05 14:19:50 -04:00
Tom Burgin
7852e69685 SantaCache fix 0 init (#158) 2017-03-22 09:38:27 -04:00
Russell Hancox
094880af50 Project: Add DevelopmentTeam configuration (#157)
This is a generated xcconfig in the Rakefile which gets included by the project
to set the DEVELOPMENT_TEAM key to keep Xcode 8 happy. The development team is
figured based on the available “Mac Developer” certificate.

Also update the way SantaCache declares a ‘zero’ value, update the
OCMock pod and add a few missing includes.
2017-03-20 16:34:59 -04:00
Tom Burgin
c3db518aca santactl/sync: use the new fcm-stream format (#156) 2017-03-20 14:42:29 -04:00
Tom Burgin
41ee0c5fdb Running without a config fixes (#154)
* common: capture fileSystemRepresentation in a local variable

* santactl/status: check for instant notification status only when there is a sync url

* s/FALSE/NO
2017-03-17 12:12:41 -04:00
Tom Burgin
ae178bc146 create default config if one does not exist (#153) 2017-03-10 17:17:52 -05:00
Tom Burgin
a2a660d483 config update and modules (#152)
* santactl/sync: https://github.com/google/santa/issues/150

* pch to modules
2017-03-09 13:02:02 -08:00
Tom Burgin
8684cc34f7 santactl/sync: use hostname for reachability (#149)
* Revert "SNTXPCConnection: make XPC debugging easier (#141)"

This reverts commit a2d6338400.

* santactl/sync: use hostname for reachability

* style update
2017-03-08 07:55:35 -08:00
Tom Burgin
0aba8b78ba disable bundle scans (#146)
* config: update to cocoapods-1.2.0 and molfcmclient 1.2

* santactl/sync: disable sync server bundle scan requests
2017-03-01 09:02:00 -08:00
Russell Hancox
5e735aa8d5 santad: Clear cache when regexes change. (#143)
When white/black-list regexes are changed clear the kernel cache so the regexes are able to take effect immediately. Fixes #142
2017-02-03 11:00:32 -05:00
Tom Burgin
a2d6338400 SNTXPCConnection: make XPC debugging easier (#141) 2017-01-31 15:36:09 -05:00
Russell Hancox
5e4b8350ab SNTXPCConnection: allow redefining invalidationHandler after connections are established (#140) 2017-01-23 11:10:13 -05:00
Tom Burgin
4a65b646df santactl status: add last successful rule sync date (#139)
* santactl status: add last successful rule sync date
2017-01-11 15:52:07 -05:00
Tom Burgin
24c715aae9 santactl sync: reachability and notification updates santad: syncd xpc updates (#138)
* santactl sync: post a notification for every matching rule and fcm message

* santactl sync: if full sync fails, retry when reachable

* santad: only allow one syncd connection at any given time
2017-01-10 16:14:15 -05:00
113 changed files with 2645 additions and 756 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ Santa.xcodeproj/xcuserdata
Santa.xcodeproj/project.xcworkspace
Santa.xcworkspace/xcuserdata
Santa.xcworkspace/xcshareddata
Source/DevelopmentTeam.xcconfig

View File

@@ -1,6 +1,6 @@
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
> /var/log/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
> /var/db/santa/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 kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/log/santa.log
? [= Facility com.google.santa] file /var/db/santa/santa.log

View File

@@ -11,6 +11,11 @@ target :santad do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
target :santabs do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
end
end
target :santactl do
@@ -18,7 +23,7 @@ target :santactl do
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'MOLFCMClient'
pod 'MOLFCMClient', '~> 1.3'
end
target :LogicTests do

View File

@@ -2,31 +2,31 @@ PODS:
- FMDB (2.6.2):
- FMDB/standard (= 2.6.2)
- FMDB/standard (2.6.2)
- MOLAuthenticatingURLSession (2.1):
- MOLAuthenticatingURLSession (2.2):
- MOLCertificate (~> 1.5)
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- MOLFCMClient (1.1):
- MOLFCMClient (1.3):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.3.1)
- OCMock (3.4)
DEPENDENCIES:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient
- MOLFCMClient (~> 1.3)
- OCMock
SPEC CHECKSUMS:
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: 2f0fd35f641bc857ee1b026021dbd759955adaa3
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
MOLFCMClient: f1684219facbffdb060ff4ab18b1825bcd4c75bc
OCMock: f3f61e6eaa16038c30caa5798c5e49d3307b6f22
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
PODFILE CHECKSUM: b20628b5933f54525daf0dcc5534512b1cb134c8
PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe
COCOAPODS: 1.0.1
COCOAPODS: 1.2.1

View File

@@ -1,3 +1,5 @@
require 'openssl'
WORKSPACE = 'Santa.xcworkspace'
DEFAULT_SCHEME = 'All'
OUTPUT_PATH = 'Build'
@@ -5,6 +7,8 @@ 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"
DEVTEAM_FILE = 'Source/DevelopmentTeam.xcconfig'
DEVTEAM_CERT_CN = 'Mac Developer'
$DISABLE_XCPRETTY = false
task :default do
@@ -44,6 +48,13 @@ task :init do
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
$DISABLE_XCPRETTY = true
end
cert_pem = `security find-certificate -p -c '#{DEVTEAM_CERT_CN}'`
cert = OpenSSL::X509::Certificate.new cert_pem
team_id = cert.subject.to_a.find {|f| f[0] == "OU"}[1]
File.open(DEVTEAM_FILE, 'w') { |f|
f.puts("// This file is auto-generated. Do not edit manually")
f.puts("DEVELOPMENT_TEAM = #{team_id}")
}
end
task :remove_existing do
@@ -95,6 +106,7 @@ namespace :install do
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
system '/usr/bin/killall -HUP syslogd'
Rake::Task['build:build'].invoke(config)
puts "Installing with configuration: #{config}"
Rake::Task['remove_existing'].invoke()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C78227531E1C3C58006EB2D6"
BuildableName = "santabs.xpc"
BlueprintName = "santabs"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C78227531E1C3C58006EB2D6"
BuildableName = "santabs.xpc"
BlueprintName = "santabs"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C78227531E1C3C58006EB2D6"
BuildableName = "santabs.xpc"
BlueprintName = "santabs"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,8 +1,8 @@
<?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">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="16D32" 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="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
@@ -16,7 +16,7 @@
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -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="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<development version="6300" identifier="xcode"/>
@@ -9,6 +9,10 @@
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
<connections>
<outlet property="applicationNameLabel" destination="qgf-Jf-cJr" id="1JX-X8-03v"/>
<outlet property="bundleHashLabel" destination="xP7-jE-NF8" id="i8B-Gs-2E3"/>
<outlet property="bundleHashTitle" destination="MhO-U0-MLR" id="KT0-bK-fpV"/>
<outlet property="foundFileCountLabel" destination="LHV-gV-vyf" id="Sr0-T2-xGx"/>
<outlet property="hashingIndicator" destination="VyY-Yg-JOe" id="Yq4-tZ-9ep"/>
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
@@ -17,14 +21,14 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa Blocked Execution" 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="439"/>
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="497" height="439"/>
<rect key="frame" x="0.0" y="0.0" width="540" height="479"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="-6" y="411" width="37" height="32"/>
<rect key="frame" x="16" y="451" width="37" height="32"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -37,7 +41,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="206" y="368" width="85" height="41"/>
<rect key="frame" x="228" y="408" width="85" height="41"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="calibratedRGB"/>
@@ -51,7 +55,7 @@
</connections>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="22" y="329" width="454" height="17"/>
<rect key="frame" x="43" y="369" width="454" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
@@ -65,58 +69,10 @@
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="VC7-bE-uHc"/>
</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="132" width="1" height="167"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="282" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<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"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</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>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="282" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" 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 horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="257" width="120" height="17"/>
<rect key="frame" x="8" y="297" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
@@ -128,9 +84,9 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="232" width="120" height="17"/>
<rect key="frame" x="8" y="272" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="3wU-P0-gAC"/>
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="adC-be-Beh">
<font key="font" metaFont="systemBold"/>
@@ -141,10 +97,10 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="257" width="294" height="17"/>
<textField toolTip="Binary Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="297" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Name" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
@@ -156,7 +112,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<rect key="frame" x="8" y="207" width="120" height="17"/>
<rect key="frame" x="8" y="247" width="142" height="17"/>
<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"/>
@@ -167,9 +123,9 @@
</textFieldCell>
</textField>
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="207" width="294" height="17"/>
<rect key="frame" x="187" y="247" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
<constraint firstAttribute="width" constant="311" id="Dem-wH-KHm"/>
</constraints>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Code signing information" placeholderString="" id="ztA-La-XgT">
<font key="font" metaFont="system"/>
@@ -185,7 +141,7 @@
</connections>
</textField>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="40" y="208" width="15" height="15"/>
<rect key="frame" x="62" y="248" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
@@ -207,7 +163,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
<rect key="frame" x="8" y="182" width="120" height="17"/>
<rect key="frame" x="8" y="222" width="142" height="17"/>
<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"/>
@@ -217,8 +173,8 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="182" width="219" height="17"/>
<textField toolTip="SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="187" y="222" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
</constraints>
@@ -234,8 +190,26 @@
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MhO-U0-MLR" userLabel="Label: Bundle Identifier">
<rect key="frame" x="8" y="197" width="142" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bundle Identifier" id="LEe-u0-52o">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.needsBundleHash" id="2kb-3z-Kyn">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
<rect key="frame" x="8" y="157" width="120" height="17"/>
<rect key="frame" x="8" y="157" width="142" height="17"/>
<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"/>
@@ -246,7 +220,7 @@
</textFieldCell>
</textField>
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="157" width="294" height="17"/>
<rect key="frame" x="187" y="157" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
@@ -269,7 +243,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<rect key="frame" x="8" y="132" width="120" height="17"/>
<rect key="frame" x="8" y="132" width="142" height="17"/>
<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"/>
@@ -279,26 +253,8 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="132" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<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" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="132" y="33" width="112" height="25"/>
<rect key="frame" x="154" y="33" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
@@ -315,8 +271,35 @@ DQ
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="272" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="113" y="80" width="315" height="29"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="256" y="33" width="110" height="25"/>
<rect key="frame" x="278" y="33" width="110" height="25"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
@@ -335,61 +318,138 @@ DQ
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="232" width="294" height="17"/>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="132" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="p1W-f9-KBX"/>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
<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" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="91" y="80" width="315" height="29"/>
<progressIndicator wantsLayer="YES" canDrawConcurrently="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
<rect key="frame" x="187" y="199" width="217" height="12"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
<constraint firstAttribute="width" constant="217" id="M22-Dv-KIP"/>
</constraints>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
</progressIndicator>
<textField toolTip="Bundle SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xP7-jE-NF8">
<rect key="frame" x="187" y="197" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="s7W-o9-2nN"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="Calculating..." id="yJa-yL-X9a">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
<binding destination="-2" name="value" keyPath="self.event.fileBundleHash" id="CnT-q6-bot"/>
</connections>
</button>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LHV-gV-vyf">
<rect key="frame" x="187" y="182" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="LUu-Vd-peN"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="1000 related binaries" id="AVM-vB-hB8">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="168" y="132" width="1" height="207"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="322" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Pav-ZA-iAu"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" 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 horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="322" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="8mA-zi-Ev7"/>
</constraints>
<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"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</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="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="0AD-PS-5V1"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="5Mr-By-PAU"/>
<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="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
<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="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-67" id="GD2-Ka-deo"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="GT2-tO-2td"/>
<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="xP7-jE-NF8" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="HUT-MI-jsR"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="Ht4-Lg-U5N"/>
<constraint firstItem="LHV-gV-vyf" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="IA0-dy-2be"/>
<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" priority="500" id="JY4-N1-j8e"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="centerX" secondItem="Iwq-Lx-rLv" secondAttribute="centerX" id="LkH-F4-Ncm"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="55" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
<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="4Li-ul-zIi" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="20" id="Seb-c0-MUL"/>
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
<constraint firstItem="LHV-gV-vyf" firstAttribute="top" secondItem="VyY-Yg-JOe" secondAttribute="bottom" id="Vjr-NX-j8V"/>
<constraint firstItem="MhO-U0-MLR" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="Vly-VE-BwU"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="top" secondItem="bDE-Tl-UHg" secondAttribute="bottom" constant="8" id="ZoS-xV-2WA"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aMJ-Wb-vRS"/>
@@ -401,22 +461,28 @@ DQ
<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="f1p-GL-O3o" firstAttribute="top" secondItem="LHV-gV-vyf" secondAttribute="bottom" constant="8" id="h4h-K3-BTd"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" priority="700" constant="8" id="hXw-6Z-lb2"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="7ua-5a-uSd" secondAttribute="trailing" constant="12" id="ioO-NJ-Jqo"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="jdk-ak-soQ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="lvJ-Rk-UT5" secondAttribute="centerY" id="jfs-YI-7Ae"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="MhO-U0-MLR" secondAttribute="trailing" constant="20" id="ke9-wW-5fr"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="top" secondItem="h6f-PY-cc0" secondAttribute="bottom" constant="25" id="lYd-VZ-lBs"/>
<constraint firstItem="VyY-Yg-JOe" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="18" id="lei-uP-T8m"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="xP7-jE-NF8" secondAttribute="bottom" priority="701" constant="8" id="oY4-e7-lsz"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="pCX-eX-erN"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="centerY" secondItem="MhO-U0-MLR" secondAttribute="centerY" id="pdC-x8-Nao"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="60" id="phL-j9-rPq"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="u1y-6V-moc"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="VyY-Yg-JOe" firstAttribute="centerY" secondItem="MhO-U0-MLR" secondAttribute="centerY" id="vB8-c5-pfO"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="YNz-ka-cBi" secondAttribute="trailing" constant="20" id="vfq-83-tKI"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="z6s-ga-iAk"/>
</constraints>
@@ -424,8 +490,9 @@ DQ
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="302.5" y="304.5"/>
<point key="canvasLocation" x="274" y="326.5"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="iXx-cu-WYe"/>
</objects>
<resources>
<image name="NSInfo" width="32" height="32"/>

View File

@@ -1,3 +0,0 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
@interface SNTAboutWindowController : NSWindowController
@property IBOutlet NSButton *moreInfoButton;

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
/**
An NSTextField subclass that provides an accessiblity label equal to:
(self.toolTip + self.stringValue) where available. It also sets the

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
///
/// Initiates and manages the connection to santad
///

View File

@@ -26,7 +26,8 @@
@property SNTAboutWindowController *aboutWindowController;
@property SNTFileWatcher *configFileWatcher;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *listener;
@property SNTXPCConnection *daemonListener;
@property SNTXPCConnection *bundleListener;
@end
@implementation SNTAppDelegate
@@ -49,18 +50,19 @@
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
self.listener.invalidationHandler = nil;
[self.listener invalidate];
self.listener = nil;
self.daemonListener.invalidationHandler = nil;
[self.daemonListener invalidate];
self.daemonListener = nil;
}];
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
[self attemptReconnection];
[self attemptDaemonReconnection];
}];
[self createConnection];
[self createDaemonConnection];
[self createBundleConnection];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
@@ -71,24 +73,24 @@
#pragma mark Connection handling
- (void)createConnection {
- (void)createDaemonConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
WEAKIFY(self);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.acceptedHandler = ^{
self.daemonListener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.daemonListener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.daemonListener.exportedObject = self.notificationManager;
self.daemonListener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
self.listener.invalidationHandler = ^{
self.daemonListener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptReconnection];
[self attemptDaemonReconnection];
};
[self.listener resume];
[self.daemonListener resume];
// Tell daemon to connect back to the above listener.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
@@ -97,12 +99,46 @@
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptReconnection];
[self attemptDaemonReconnection];
}
}
- (void)attemptReconnection {
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
- (void)attemptDaemonReconnection {
[self performSelectorInBackground:@selector(createDaemonConnection) withObject:nil];
}
- (void)createBundleConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
WEAKIFY(self);
// Create listener for return connection from the bundle service.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.bundleListener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.bundleListener.exportedInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
self.bundleListener.exportedObject = self.notificationManager;
self.bundleListener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
self.bundleListener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptBundleReconnection];
};
[self.bundleListener resume];
// Tell santabs to connect back to the above listener.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] setBundleNotificationListener:listener.endpoint];
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptBundleReconnection];
}
}
- (void)attemptBundleReconnection {
[self performSelectorInBackground:@selector(createBundleConnection) withObject:nil];
}
#pragma mark Menu Management

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
///
/// An NSPanel that can become key/main and can fade in/out.
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
@class SNTStoredEvent;
@protocol SNTMessageWindowControllerDelegate
@@ -29,6 +31,40 @@
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashLabel;
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashTitle;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// The execution event that this window is for
///
@property(readonly) SNTStoredEvent *event;
///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSProgress *progress;
///
/// The delegate to inform when the notification is dismissed
///

View File

@@ -14,7 +14,7 @@
#import "SNTMessageWindowController.h"
#import <SecurityInterface/SFCertificatePanel.h>
@import SecurityInterface.SFCertificatePanel;
#import "MOLCertificate.h"
#import "SNTBlockMessage.h"
@@ -24,9 +24,6 @@
#import "SNTStoredEvent.h"
@interface SNTMessageWindowController ()
/// The execution event that this window is for
@property SNTStoredEvent *event;
/// The custom message to display for this event
@property(copy) NSString *customMessage;
@@ -36,13 +33,9 @@
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
@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;
@property(weak) IBOutlet NSTextField *applicationNameLabel;
/// Linked to checkbox in UI to prevent future notifications for this binary.
@property BOOL silenceFutureNotifications;
@@ -55,10 +48,34 @@
if (self) {
_event = event;
_customMessage = message;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
if (progress.fractionCompleted != 0.0) {
self.hashingIndicator.indeterminate = NO;
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
@@ -73,6 +90,18 @@
}
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];
[self.foundFileCountLabel removeFromSuperview];
} else {
self.openEventButton.enabled = NO;
self.hashingIndicator.indeterminate = YES;
[self.hashingIndicator startAnimation:self];
self.bundleHashLabel.hidden = YES;
self.foundFileCountLabel.stringValue = @"";
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
@@ -83,6 +112,7 @@
}
- (IBAction)closeWindow:(id)sender {
[self.progress cancel];
[(SNTMessageWindow *)self.window fadeOut:sender];
}

View File

@@ -12,12 +12,15 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
#import "SNTMessageWindowController.h"
#import "SNTXPCNotifierInterface.h"
///
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.
///
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate, SNTNotifierXPC>
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate,
SNTNotifierXPC, SNTBundleNotifierXPC>
@end

View File

@@ -18,13 +18,27 @@
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTNotificationManager ()
/// The currently displayed notification
@property SNTMessageWindowController *currentWindowController;
/// The queue of pending notifications
@property(readonly) NSMutableArray *pendingNotifications;
/// The connection to the bundle service
@property SNTXPCConnection *bundleServiceConnection;
/// A semaphore to block bundle hashing until a connection is established
@property dispatch_semaphore_t bundleServiceSema;
// A serial queue for holding hashBundleBinaries requests
@property dispatch_queue_t hashBundleBinariesQueue;
@end
@implementation SNTNotificationManager
@@ -35,6 +49,9 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
self = [super init];
if (self) {
_pendingNotifications = [[NSMutableArray alloc] init];
_bundleServiceSema = dispatch_semaphore_create(0);
_hashBundleBinariesQueue = dispatch_queue_create("com.google.santagui.hashbundlebinaries",
DISPATCH_QUEUE_SERIAL);
}
return self;
}
@@ -48,7 +65,16 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
if ([self.pendingNotifications count]) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
} else {
// Tear down the bundle service
self.bundleServiceSema = dispatch_semaphore_create(0);
[self.bundleServiceConnection invalidate];
self.bundleServiceConnection = nil;
[NSApp hide:self];
}
}
@@ -65,7 +91,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
[ud setObject:d forKey:silencedNotificationsKey];
}
#pragma mark SNTNotifierXPC protocol method
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
NSUserNotification *un = [[NSUserNotification alloc] init];
@@ -131,6 +157,11 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
}
});
}
@@ -143,4 +174,100 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
#pragma mark SNTBundleNotifierXPC protocol methods
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount {
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
dispatch_async(dispatch_get_main_queue(), ^{
self.currentWindowController.foundFileCountLabel.stringValue =
[NSString stringWithFormat:@"%llu binaries / %llu %@",
binaryCount, hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
});
}
}
- (void)setBundleServiceListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
[c resume];
self.bundleServiceConnection = c;
WEAKIFY(self);
self.bundleServiceConnection.invalidationHandler = ^{
STRONGIFY(self);
if (self.currentWindowController) {
[self updateBlockNotification:self.currentWindowController.event withBundleHash:nil];
}
};
dispatch_semaphore_signal(self.bundleServiceSema);
}
#pragma mark SNTBundleNotifierXPC helper methods
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event {
self.currentWindowController.foundFileCountLabel.stringValue = @"Searching for files...";
// Wait a max of 6 secs for the bundle service. Should the bundle service fall over, it will
// reconnect within 5 secs. Otherwise abandon bundle hashing and display the blockable event.
if (dispatch_semaphore_wait(self.bundleServiceSema,
dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC))) {
[self updateBlockNotification:event withBundleHash:nil];
return;
}
// Let all future requests flow, until the connection is terminated and we go back to waiting.
dispatch_semaphore_signal(self.bundleServiceSema);
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
[self.currentWindowController.progress becomeCurrentWithPendingUnitCount:100];
// Start hashing. Progress is reported to the root NSProgress (currentWindowController.progress).
[[self.bundleServiceConnection remoteObjectProxy]
hashBundleBinariesForEvent:event
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
// Revert to displaying the blockable event if we fail to calculate the bundle hash
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
event.fileBundleHash = bh;
event.fileBundleBinaryCount = @(events.count);
event.fileBundleHashMilliseconds = ms;
event.fileBundleExecutableRelPath = [events.firstObject fileBundleExecutableRelPath];
for (SNTStoredEvent *se in events) {
se.fileBundleHash = bh;
se.fileBundleBinaryCount = @(events.count);
se.fileBundleHashMilliseconds = ms;
}
// Send the results to santad. It will decide if they need to be synced.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] syncBundleEvent:event relatedEvents:events];
// Update the UI with the bundle hash. Also make the openEventButton available.
[self updateBlockNotification:event withBundleHash:bh];
}];
[self.currentWindowController.progress resignCurrent];
}
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
if (bundleHash) {
[self.currentWindowController.bundleHashLabel setHidden:NO];
} else {
[self.currentWindowController.bundleHashLabel removeFromSuperview];
[self.currentWindowController.bundleHashTitle removeFromSuperview];
}
self.currentWindowController.event.fileBundleHash = bundleHash;
[self.currentWindowController.foundFileCountLabel removeFromSuperview];
[self.currentWindowController.hashingIndicator setHidden:YES];
[self.currentWindowController.openEventButton setEnabled:YES];
}
});
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
#import "SNTAppDelegate.h"
int main(int argc, const char *argv[]) {

View File

@@ -12,6 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifdef SANTAGUI
@import Cocoa;
#else
@import Foundation;
#endif
@class SNTStoredEvent;
@interface SNTBlockMessage : NSObject

View File

@@ -109,8 +109,9 @@
if (!formatStr.length) return nil;
if (event.fileSHA256) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileSHA256];
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
}
if (event.executingUser) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// These enums are used in various places throughout the Santa client code.
/// The integer values are also stored in the database and so shouldn't be changed.

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///
@@ -131,21 +133,20 @@ extern NSString *const kDefaultConfigFilePath;
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// If YES, mid-execution event uploads are skipped.
/// This property is never stored on disk.
///
@property BOOL syncBackOff;
///
/// The machine owner.
///
@property(readonly, nonatomic) NSString *machineOwner;
///
/// The last date of successful sync.
/// The last date of a successful full sync.
///
@property(nonatomic) NSDate *syncLastSuccess;
@property(nonatomic) NSDate *fullSyncLastSuccess;
///
/// The last date of a successful rule sync.
///
@property(nonatomic) NSDate *ruleSyncLastSuccess;
///
/// If YES a clean sync is required.
@@ -157,6 +158,12 @@ extern NSString *const kDefaultConfigFilePath;
///
@property(readonly, nonatomic) NSString *machineID;
///
/// If YES, enables bundle detection for blocked events. This property is not stored on disk.
/// Its value is set by a sync server that supports bundles. Defaults to NO.
///
@property BOOL bundlesEnabled;
#pragma mark Server Auth Settings
///

View File

@@ -52,7 +52,8 @@ static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kSyncLastSuccess = @"SyncLastSuccess";
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
@@ -111,7 +112,7 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return (SNTClientMode)cm;
} else {
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
self.clientMode = SNTClientModeMonitor;
return SNTClientModeMonitor;
}
}
@@ -259,12 +260,22 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kServerAuthRootsFileKey];
}
- (NSDate *)syncLastSuccess {
return self.configData[kSyncLastSuccess];
- (NSDate *)fullSyncLastSuccess {
return self.configData[kFullSyncLastSuccess];
}
- (void)setSyncLastSuccess:(NSDate *)syncLastSuccess {
self.configData[kSyncLastSuccess] = syncLastSuccess;
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
self.configData[kFullSyncLastSuccess] = fullSyncLastSuccess;
[self saveConfigToDisk];
self.ruleSyncLastSuccess = fullSyncLastSuccess;
}
- (NSDate *)ruleSyncLastSuccess {
return self.configData[kRuleSyncLastSuccess];
}
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
[self saveConfigToDisk];
}
@@ -317,7 +328,14 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
- (void)reloadConfigData {
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:self.configFilePath]) return;
if (![fm fileExistsAtPath:self.configFilePath]) {
// As soon as saveConfigToDisk is called, reloadConfigData will be called again because
// of the SNTFileWatchers on the config path. No need to use dictionaryWithCapacity: here.
self.configData = [NSMutableDictionary dictionary];
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
[self saveConfigToDisk];
return;
};
NSError *error;
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple function to check and drop root privileges.
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Represents a binary on disk, providing access to details about that binary
/// such as the SHA-1, SHA-256, Info.plist and the Mach-O data.
@@ -36,6 +38,18 @@
///
- (instancetype)initWithPath:(NSString *)path;
///
/// Initializer for already resolved paths.
///
/// @param path The path of the file this instance is to represent. The path will
/// not be converted and will be used as is. If the path is not a regular file this method will
/// return nil and fill in an error.
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
/// describing the problem.
///
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error;
///
/// @return Path of this file.
///
@@ -114,6 +128,21 @@
///
- (BOOL)isMissingPageZero;
///
/// If set to YES, the bundle* and infoPlist methods will search for and use the highest NSBundle
/// found in the tree. Defaults to NO, which uses the first found bundle, if any.
///
/// @example:
/// An SNTFileInfo object that represents
/// /Applications/Photos.app/Contents/XPCServices/com.apple.Photos.librarychooserservice.xpc
/// useAncestorBundle is set to YES
/// /Applications/Photos.app will be used to get data backing all the bundle methods
///
/// @note: The NSBundle object backing the bundle* and infoPlist methods is cached once found.
/// Setting the useAncestorBundle propery will clear this cache and force a re-search.
///
@property(nonatomic) BOOL useAncestorBundle;
///
/// @return An NSBundle if this file is part of a bundle.
///

View File

@@ -59,23 +59,43 @@
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
self = [super init];
if (self) {
NSBundle *bndl;
_path = [self resolvePath:path bundle:&bndl];
_bundleRef = bndl;
if (_path.length == 0) {
_path = path;
if (!_path.length) {
if (error) {
NSString *errStr = @"Unable to resolve empty path";
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
NSString *errStr = @"Unable to use empty path";
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:260
code:270
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
struct stat fileStat;
lstat(_path.UTF8String, &fileStat);
if (!((S_IFMT & fileStat.st_mode) == S_IFREG)) {
if (error) {
NSString *errStr = [NSString stringWithFormat:@"Non regular file: %s", strerror(errno)];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:290
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
_fileSize = fileStat.st_size;
if (_fileSize == 0) return nil;
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (pwd) {
_fileOwnerHomeDir = @(pwd->pw_dir);
}
}
int fd = open([_path UTF8String], O_RDONLY | O_CLOEXEC);
if (fd < 0) {
if (error) {
@@ -87,24 +107,29 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return nil;
}
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
struct stat fileStat;
fstat(_fileHandle.fileDescriptor, &fileStat);
_fileSize = fileStat.st_size;
if (_fileSize == 0) return nil;
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (pwd) {
_fileOwnerHomeDir = @(pwd->pw_dir);
}
}
}
return self;
}
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
NSBundle *bndl;
NSString *resolvedPath = [self resolvePath:path bundle:&bndl];
if (!resolvedPath.length) {
if (error) {
NSString *errStr = @"Unable to resolve empty path";
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:260
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
self = [self initWithResolvedPath:resolvedPath error:error];
if (self && bndl) _bundleRef = bndl;
return self;
}
- (instancetype)initWithPath:(NSString *)path {
return [self initWithPath:path error:NULL];
}
@@ -280,34 +305,34 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
///
/// Rationale: An NSBundle has a method executablePath for discovering the main binary within a
/// bundle but provides no way to get an NSBundle object when only the executablePath is known.
/// Also a bundle can contain multiple binaries within the MacOS folder and we want any of these
/// Also a bundle can contain multiple binaries within its subdirectories and we want any of these
/// to count as being part of the bundle.
///
/// This method relies on executable bundles being laid out as follows:
/// This method walks up the path until a bundle is found, if any.
///
/// @code
/// Bundle.app/
/// Contents/
/// MacOS/
/// executable
/// @endcode
///
/// If @c self.path is the full path to @c executable above, this method would return an
/// NSBundle reference for Bundle.app.
/// @param ancestor YES this will return the highest NSBundle found in the tree. No will return the
/// the lowest.
///
-(NSBundle *)findBundleWithAncestor:(BOOL)ancestor {
NSBundle *bundle;
NSMutableArray *pathComponents = [[self.path pathComponents] mutableCopy];
// Ignore the root path "/", for some reason this is considered a bundle.
while (pathComponents.count > 1) {
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
bundle = bndl;
if (!ancestor) break;
}
[pathComponents removeLastObject];
}
return bundle;
}
- (NSBundle *)bundle {
if (!self.bundleRef) {
self.bundleRef = (NSBundle *)[NSNull null];
// Check that the full path is at least 4-levels deep:
// e.g: /Calendar.app/Contents/MacOS/Calendar
NSArray *pathComponents = [self.path pathComponents];
NSUInteger pathComponentsCount = pathComponents.count;
if (pathComponentsCount < 4) return nil;
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponentsCount - 3)];
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = bndl;
self.bundleRef =
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
}
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
}
@@ -316,6 +341,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return [self.bundle bundlePath];
}
- (void)setUseAncestorBundle:(BOOL)useAncestorBundle {
if (self.useAncestorBundle != useAncestorBundle) {
self.bundleRef = nil;
self.infoDict = nil;
}
_useAncestorBundle = useAncestorBundle;
}
- (NSDictionary *)infoPlist {
if (!self.infoDict) {
NSDictionary *d = [self embeddedPlist];
@@ -638,7 +671,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
BOOL directory;
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
return nil;
} else if (directory) {
} else if (directory && ![path isEqualToString:@"/"]) {
NSBundle *bndl = [NSBundle bundleWithPath:path];
if (bundle) *bundle = bndl;
return [bndl executablePath];

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple file watching class using dispatch sources. Will automatically
/// reload the watch if the file is deleted and continue watching for

View File

@@ -18,7 +18,7 @@
@interface SNTFileWatcher ()
@property NSString *filePath;
@property(strong) void (^handler)(unsigned long);
@property(copy) void (^handler)(unsigned long);
@property dispatch_source_t source;
@end
@@ -52,7 +52,8 @@
dispatch_async(queue, ^{
int fd = -1;
while ((fd = open([self.filePath fileSystemRepresentation], O_EVTONLY | O_CLOEXEC)) < 0) {
const char *filePath = [self.filePath fileSystemRepresentation];
while ((fd = open(filePath, O_EVTONLY | O_CLOEXEC)) < 0) {
usleep(200000); // wait 200ms
}
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);

View File

@@ -34,6 +34,8 @@
#else // KERNEL
@import Foundation;
typedef enum : NSUInteger {
LOG_LEVEL_ERROR,
LOG_LEVEL_WARN,

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///
@@ -34,6 +36,28 @@
///
@property NSString *filePath;
///
/// Set to YES if the event is a part of a bundle. When an event is passed to SantaGUI this propery
/// will be used as an indicator to to kick off bundle hashing as necessary. Default value is NO.
///
@property BOOL needsBundleHash;
///
/// If the executed file was part of a bundle, this is the calculated hash of all the nested
/// executables within the bundle.
///
@property NSString *fileBundleHash;
///
/// If the executed file was part of a bundle, this is the time in ms it took to hash the bundle.
///
@property NSNumber *fileBundleHashMilliseconds;
///
/// If the executed file was part of a bundle, this is the total count of related mach-o binaries.
///
@property NSNumber *fileBundleBinaryCount;
///
/// If the executed file was part of the bundle, this is the CFBundleDisplayName, if it exists
/// or the CFBundleName if not.
@@ -45,6 +69,11 @@
///
@property NSString *fileBundlePath;
///
/// The relative path to the bundle's main executable.
///
@property NSString *fileBundleExecutableRelPath;
///
/// If the executed file was part of the bundle, this is the CFBundleID.
///

View File

@@ -33,8 +33,13 @@
ENCODE(self.fileSHA256, @"fileSHA256");
ENCODE(self.filePath, @"filePath");
ENCODE(@(self.needsBundleHash), @"needsBundleHash");
ENCODE(self.fileBundleHash, @"fileBundleHash");
ENCODE(self.fileBundleHashMilliseconds, @"fileBundleHashMilliseconds");
ENCODE(self.fileBundleBinaryCount, @"fileBundleBinaryCount");
ENCODE(self.fileBundleName, @"fileBundleName");
ENCODE(self.fileBundlePath, @"fileBundlePath");
ENCODE(self.fileBundleExecutableRelPath, @"fileBundleExecutableRelPath");
ENCODE(self.fileBundleID, @"fileBundleID");
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
@@ -72,8 +77,13 @@
_fileSHA256 = DECODE(NSString, @"fileSHA256");
_filePath = DECODE(NSString, @"filePath");
_needsBundleHash = [DECODE(NSNumber, @"needsBundleHash") boolValue];
_fileBundleHash = DECODE(NSString, @"fileBundleHash");
_fileBundleHashMilliseconds = DECODE(NSNumber, @"fileBundleHashMilliseconds");
_fileBundleBinaryCount = DECODE(NSNumber, @"fileBundleBinaryCount");
_fileBundleName = DECODE(NSString, @"fileBundleName");
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
_fileBundleExecutableRelPath = DECODE(NSString, @"fileBundleExecutableRelPath");
_fileBundleID = DECODE(NSString, @"fileBundleID");
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple class for fetching system information
///

View File

@@ -0,0 +1,57 @@
/// Copyright 2017 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 Foundation;
@class SNTStoredEvent;
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
@protocol SNTBundleServiceXPC
///
/// @param listener The listener to connect back to the SantaGUI.
///
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Hash a bundle for an event. The SNTBundleHashBlock will be called with nil parameters if a
/// failure or cancellation occurs.
///
/// @param event The event that includes the fileBundlePath to be hashed. This method will
/// attempt to to find and use the ancestor bundle as a starting point.
/// @param reply A SNTBundleHashBlock to be executed upon completion or cancellation.
///
/// @note If there is a current NSProgress when called this method will report back its progress.
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
@end
@interface SNTXPCBundleServiceInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTBundleServiceXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
///
+ (NSXPCInterface *)bundleServiceInterface;
///
/// Returns the MachService ID for this service.
///
+ (NSString *)serviceId;
@end

View File

@@ -0,0 +1,36 @@
/// Copyright 2017 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 "SNTXPCBundleServiceInterface.h"
#import "SNTStoredEvent.h"
@implementation SNTXPCBundleServiceInterface
+ (NSXPCInterface *)bundleServiceInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(hashBundleBinariesForEvent:reply:)
argumentIndex:1
ofReply:YES];
return r;
}
+ (NSString *)serviceId {
return @"com.google.santabs";
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
/**
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
validation of connecting clients and forced connection establishment.
@@ -63,13 +65,20 @@
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
/**
Initializer a new client to a service exported by a LaunchDaemon.
Initialize a new client to a service exported by a LaunchDaemon.
@param name MachService name
@param privileged Use YES if the server is running as root.
*/
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
/**
Initialize a new client to a service within a bundle.
@param name service name
*/
- (nullable instancetype)initClientWithServiceName:(nonnull NSString *)name;
/**
Initialize a new client with a listener endpoint sent from another process.

View File

@@ -95,6 +95,17 @@
return self;
}
- (instancetype)initClientWithServiceName:(NSString *)name {
self = [super init];
if (self) {
_currentConnection = [[NSXPCConnection alloc] initWithServiceName:name];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
@@ -113,8 +124,10 @@
// send a message to the listener to finish establishing the connection
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
self.currentConnection.remoteObjectInterface = self.validationInterface;
self.currentConnection.interruptionHandler = self.invalidationHandler;
self.currentConnection.invalidationHandler = self.invalidationHandler;
self.currentConnection.interruptionHandler = self.currentConnection.invalidationHandler = ^{
STRONGIFY(self);
if (self.invalidationHandler) self.invalidationHandler();
};
[self.currentConnection resume];
[[self.currentConnection remoteObjectProxy] connectWithReply:^{
STRONGIFY(self);

View File

@@ -12,11 +12,14 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import <MOLCertificate/MOLCertificate.h>
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTRule;
@class SNTStoredEvent;
@@ -55,13 +58,13 @@
/// @param filePath A Path to the file, can be nil.
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
/// be calculated by this method from the filePath.
/// @param signingCertificate A MOLCertificate object, can be nil.
/// @param certificateSHA256 A SHA256 hash of the signing certificate, can be nil.
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
/// returned. Binary rules take precedence over cert rules.
///
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
signingCertificate:(MOLCertificate *)signingCertificate
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply;
///
@@ -73,23 +76,32 @@
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setRuleSyncLastSuccess:(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;
- (void)bundlesEnabled:(void (^)(BOOL))reply;
- (void)setBundlesEnabled:(BOOL)bundlesEnabled reply:(void (^)())reply;
///
/// GUI Ops
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)pushNotifications:(void (^)(BOOL))reply;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply;
///
/// Bundle Ops
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
- (void)syncBundleEvent:(SNTStoredEvent *)event relatedEvents:(NSArray<SNTStoredEvent *> *)events;
@end
@interface SNTXPCControlInterface : NSObject

View File

@@ -37,6 +37,16 @@
argumentIndex:0
ofReply:NO];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(hashBundleBinariesForEvent:reply:)
argumentIndex:1
ofReply:YES];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(syncBundleEvent:relatedEvents:)
argumentIndex:1
ofReply:NO];
return r;
}

View File

@@ -12,7 +12,10 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTStoredEvent;
@@ -23,6 +26,16 @@
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
@end
/// Protocol implemented by SantaGUI and utilized by santabs
@protocol SNTBundleNotifierXPC
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount;
- (void)setBundleServiceListener:(NSXPCListenerEndpoint *)listener;
@end
@interface SNTXPCNotifierInterface : NSObject
///
@@ -31,4 +44,10 @@
///
+ (NSXPCInterface *)notifierInterface;
///
/// @return an initialized NSXPCInterface for the SNTBundleNotifierXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)bundleNotifierInterface;
@end

View File

@@ -20,4 +20,8 @@
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
}
+ (NSXPCInterface *)bundleNotifierInterface {
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleNotifierXPC)];
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
@class SNTStoredEvent;
@@ -19,7 +21,8 @@
/// Protocol implemented by santactl and utilized by santad
@protocol SNTSyncdXPC
- (void)postEventToSyncServer:(SNTStoredEvent *)event;
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event reply:(void (^)(BOOL))reply;
- (void)postBundleEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end

View File

@@ -14,10 +14,19 @@
#import "SNTXPCSyncdInterface.h"
#import "SNTStoredEvent.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postBundleEventsToSyncServer:)
argumentIndex:0
ofReply:NO];
return r;
}
@end

View File

@@ -247,7 +247,7 @@ template<class T> class SantaCache {
/**
Holder for a 'zero' entry for the current type
*/
T zero_ = {};
const T zero_ = T();
/**
Special bucket used when automatically clearing due to size

View File

@@ -206,11 +206,13 @@ void SantaDecisionManager::AddToCache(
decision_cache_->remove(identifier);
}
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
}
void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
decision_cache_->remove(identifier);
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
}
@@ -237,13 +239,8 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
if (RESPONSE_VALID(result)) {
if (result == ACTION_RESPOND_DENY) {
auto diff_time = GetCurrentUptime();
if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
}
if (decision_time < diff_time) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache_->remove(identifier);
return ACTION_UNSET;
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>santabs</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>XPCService</key>
<dict>
<key>ServiceType</key>
<string>Application</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
/// Copyright 2017 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 Foundation;
#import "SNTXPCBundleServiceInterface.h"
@interface SNTBundleService : NSObject<SNTBundleServiceXPC>
@end

View File

@@ -0,0 +1,303 @@
/// Copyright 2017 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 "SNTBundleService.h"
#import <CommonCrypto/CommonDigest.h>
#import <pthread/pthread.h>
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import "SNTFileInfo.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTBundleService ()
@property SNTXPCConnection *notifierConnection;
@property SNTXPCConnection *listener;
@property(nonatomic) dispatch_queue_t queue;
@end
@implementation SNTBundleService
- (instancetype)init {
self = [super init];
if (self) {
_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
}
return self;
}
#pragma mark Connection handling
// Create a listener for SantaGUI to connect
- (void)createConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Create listener for return connection from SantaGUI.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
self.listener.exportedObject = self;
self.listener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
// Exit when SantaGUI is done with us.
self.listener.invalidationHandler = ^{
exit(0);
};
[self.listener resume];
// Tell SantaGUI to connect back to the above listener.
[[self.notifierConnection remoteObjectProxy] setBundleServiceListener:listener.endpoint];
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptReconnection];
}
}
- (void)attemptReconnection {
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
}
#pragma mark SNTBundleServiceXPC Methods
// Connect to the SantaGUI
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
[c resume];
self.notifierConnection = c;
dispatch_async(self.queue, ^{
[self createConnection];
});
}
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
reply:(SNTBundleHashBlock)reply {
NSProgress *progress =
[NSProgress currentProgress] ? [NSProgress progressWithTotalUnitCount:100] : nil;
NSDate *startTime = [NSDate date];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(self.queue, ^{
// Use the highest bundle we can find. Save and reuse the bundle infomation when creating
// the related binary events.
SNTFileInfo *b = [[SNTFileInfo alloc] initWithPath:event.fileBundlePath];
b.useAncestorBundle = YES;
event.fileBundlePath = b.bundlePath;
event.fileBundleID = b.bundleIdentifier;
event.fileBundleName = b.bundleName;
event.fileBundleVersion = b.bundleVersion;
event.fileBundleVersionString = b.bundleShortVersionString;
// For most apps this should be "Contents/MacOS/AppName"
if (b.bundle.executablePath.length > b.bundlePath.length) {
event.fileBundleExecutableRelPath =
[b.bundle.executablePath substringFromIndex:b.bundlePath.length + 1];
}
NSDictionary *relatedEvents = [self findRelatedBinaries:event progress:progress];
NSString *bundleHash = [self calculateBundleHashFromSHA256Hashes:relatedEvents.allKeys
progress:progress];
NSNumber *ms = [NSNumber numberWithDouble:[startTime timeIntervalSinceNow] * -1000.0];
reply(bundleHash, relatedEvents.allValues, ms);
dispatch_semaphore_signal(sema);
});
// Master timeout of 10 min. Don't block the calling thread. NSProgress updates will be coming
// in over this thread.
dispatch_async(self.queue, ^{
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 600 * NSEC_PER_SEC))) {
[progress cancel];
}
});
}
#pragma mark Internal Methods
/**
Find binaries within a bundle given the bundle's event. It will run until a timeout occurs,
or until the NSProgress is cancelled. Search is done within the bundle concurrently.
@param event The SNTStoredEvent to begin searching.
@return An NSDictionary object with keys of fileSHA256 and values of SNTStoredEvent objects.
*/
- (NSDictionary *)findRelatedBinaries:(SNTStoredEvent *)event progress:(NSProgress *)progress {
// Find all files and folders within the fileBundlePath
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *subpaths = [fm subpathsOfDirectoryAtPath:event.fileBundlePath error:NULL];
// This array is used to store pointers to executable SNTFileInfo objects. There will be one block
// dispatched per file in dirEnum. These blocks will write pointers to this array concurrently.
// No locks are used since every file has a slot.
//
// Xcode.app has roughly 500k files, 8bytes per pointer is ~4MB for this array. This size to space
// ratio seems appropriate as Xcode.app is in the upper bounds of bundle size.
__block void **fis = calloc(subpaths.count, sizeof(void *));
// Counts used as additional progress information in SantaGUI
__block volatile int64_t binaryCount = 0;
__block volatile int64_t sentBinaryCount = 0;
// Account for 80% of the work
NSProgress *p;
if (progress) {
[progress becomeCurrentWithPendingUnitCount:80];
p = [NSProgress progressWithTotalUnitCount:subpaths.count * 100];
}
// Dispatch a block for every file in dirEnum.
dispatch_apply(subpaths.count, self.queue, ^(size_t i) {
@autoreleasepool {
if (progress.isCancelled) return;
dispatch_sync(dispatch_get_main_queue(), ^{
p.completedUnitCount++;
if (progress && ((i % 500) == 0 || binaryCount > sentBinaryCount)) {
sentBinaryCount = binaryCount;
[[self.notifierConnection remoteObjectProxy] updateCountsForEvent:event
binaryCount:binaryCount
fileCount:i
hashedCount:0];
}
});
NSString *subpath = subpaths[i];
NSString *file =
[event.fileBundlePath stringByAppendingPathComponent:subpath].stringByStandardizingPath;
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithResolvedPath:file error:NULL];
if (!fi.isExecutable) return;
fis[i] = (__bridge_retained void *)fi;
OSAtomicIncrement64Barrier(&binaryCount);
}
});
[progress resignCurrent];
NSMutableArray *fileInfos = [NSMutableArray arrayWithCapacity:binaryCount];
for (NSUInteger i = 0; i < subpaths.count; i++) {
if (fis[i]) [fileInfos addObject:(__bridge_transfer SNTFileInfo *)fis[i]];
}
free(fis);
return [self generateEventsFromBinaries:fileInfos blockingEvent:event progress:progress];
}
- (NSDictionary *)generateEventsFromBinaries:(NSArray *)fis
blockingEvent:(SNTStoredEvent *)event
progress:(NSProgress *)progress {
if (progress.isCancelled) return nil;
NSMutableDictionary *relatedEvents = [NSMutableDictionary dictionaryWithCapacity:fis.count];
// Account for 15% of the work
NSProgress *p;
if (progress) {
[progress becomeCurrentWithPendingUnitCount:15];
p = [NSProgress progressWithTotalUnitCount:fis.count * 100];
}
dispatch_apply(fis.count, self.queue, ^(size_t i) {
@autoreleasepool {
if (progress.isCancelled) return;
SNTFileInfo *fi = fis[i];
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.filePath = fi.path;
se.fileSHA256 = fi.SHA256;
se.occurrenceDate = [NSDate distantFuture];
se.decision = SNTEventStateBundleBinary;
se.fileBundlePath = event.fileBundlePath;
se.fileBundleExecutableRelPath = event.fileBundleExecutableRelPath;
se.fileBundleID = event.fileBundleID;
se.fileBundleName = event.fileBundleName;
se.fileBundleVersion = event.fileBundleVersion;
se.fileBundleVersionString = event.fileBundleVersionString;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
se.signingChain = cs.certificates;
dispatch_sync(dispatch_get_main_queue(), ^{
relatedEvents[se.fileSHA256] = se;
p.completedUnitCount++;
if (progress) {
[[self.notifierConnection remoteObjectProxy] updateCountsForEvent:event
binaryCount:fis.count
fileCount:0
hashedCount:i];
}
});
}
});
[progress resignCurrent];
return relatedEvents;
}
- (NSString *)calculateBundleHashFromSHA256Hashes:(NSArray *)hashes
progress:(NSProgress *)progress {
if (!hashes.count) return nil;
// Account for 5% of the work
NSProgress *p;
if (progress) {
[progress becomeCurrentWithPendingUnitCount:5];
p = [NSProgress progressWithTotalUnitCount:5 * 100];
}
NSMutableArray *sortedHashes = [hashes mutableCopy];
[sortedHashes sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
NSString *sha256Hashes = [sortedHashes componentsJoinedByString:@""];
CC_SHA256_CTX c256;
CC_SHA256_Init(&c256);
CC_SHA256_Update(&c256, (const void *)sha256Hashes.UTF8String, (CC_LONG)sha256Hashes.length);
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &c256);
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
NSString *sha256 = [[NSString alloc] initWithFormat:SHA256FormatString,
digest[0], digest[1], digest[2], digest[3],
digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19],
digest[20], digest[21], digest[22], digest[23],
digest[24], digest[25], digest[26], digest[27],
digest[28], digest[29], digest[30], digest[31]];
p.completedUnitCount++;
[progress resignCurrent];
return sha256;
}
@end

27
Source/santabs/main.m Normal file
View File

@@ -0,0 +1,27 @@
/// Copyright 2017 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 Foundation;
#import "SNTBundleService.h"
#import "SNTXPCBundleServiceInterface.h"
#import "SNTXPCConnection.h"
int main(int argc, const char *argv[]) {
SNTXPCConnection *c =
[[SNTXPCConnection alloc] initServerWithListener:[NSXPCListener serviceListener]];
c.exportedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
c.exportedObject = [[SNTBundleService alloc] init];
[c resume];
}

View File

@@ -0,0 +1,79 @@
/// Copyright 2017 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 "SNTCommandController.h"
#import "SNTFileInfo.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandBundleInfo : NSObject<SNTCommand>
@end
@implementation SNTCommandBundleInfo
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"bundleinfo")
#endif
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
}
+ (NSString *)shortHelpText {
return @"Searches a bundle for binaries";
}
+ (NSString *)longHelpText {
return @"Searches a bundle for binaries";
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
NSError *error;
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:arguments.firstObject error:&error];
if (!fi) {
printf("%s\n", error.description.UTF8String);
exit(1);
} else if (!fi.bundle) {
printf("Not a bundle\n");
exit(2);
}
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.fileBundlePath = fi.bundlePath;
[[daemonConn remoteObjectProxy] hashBundleBinariesForEvent:se
reply:^(NSString *hash,
NSArray<SNTStoredEvent *> *events,
NSNumber *time) {
printf("Hashing time: %llu ms\n", time.unsignedLongLongValue);
printf("%lu events found\n", events.count);
printf("BundleHash: %s\n", hash.UTF8String);
for (SNTStoredEvent *event in events) {
printf("BundleID: %s \n\tSHA-256: %s \n\tPath: %s\n",
event.fileBundleID.UTF8String, event.fileSHA256.UTF8String, event.filePath.UTF8String);
}
exit(0);
}];
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import "SNTLogging.h"

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import <MOLCertificate/MOLCertificate.h>
@@ -279,7 +281,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
[[fi.daemonConn remoteObjectProxy] decisionForFilePath:fi.path(fi)
fileSHA256:fi.propertyMap[kSHA256](fi)
signingCertificate:fi.csc.leafCertificate
certificateSHA256:fi.csc.leafCertificate.SHA256
reply:^(SNTEventState state) {
if (state) s = state;
dispatch_semaphore_signal(sema);

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import "SNTLogging.h"

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#include "SNTLogging.h"
@@ -42,7 +44,7 @@ REGISTER_COMMAND_NAME(@"rule")
}
+ (NSString *)shortHelpText {
return @"Manually add/remove rules.";
return @"Manually add/remove/check rules.";
}
+ (NSString *)longHelpText {
@@ -52,14 +54,17 @@ REGISTER_COMMAND_NAME(@"rule")
@" --blacklist: add to blacklist\n"
@" --silent-blacklist: add to silent blacklist\n"
@" --remove: remove existing rule\n"
@" --check: check for an 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"
@" Does not work with --check. Use the fileinfo verb to check.\n"
@" the rule state of a file.\n"
@" --sha256 {sha256}: hash to add/remove/check\n"
@"\n"
@" Optionally:\n"
@" --certificate: add certificate rule instead of binary\n"
@" --certificate: add or check a certificate sha256 rule instead of binary\n"
@" --message {message}: custom message\n");
}
@@ -71,7 +76,7 @@ REGISTER_COMMAND_NAME(@"rule")
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
SNTConfigurator *config = [SNTConfigurator configurator];
if ([config syncBaseURL] != nil) {
if ([config syncBaseURL] && ![arguments containsObject:@"--check"]) {
printf("SyncBaseURL is set, rules are managed centrally.\n");
exit(1);
}
@@ -81,6 +86,7 @@ REGISTER_COMMAND_NAME(@"rule")
newRule.type = SNTRuleTypeBinary;
NSString *path;
BOOL check = NO;
// Parse arguments
for (NSUInteger i = 0; i < arguments.count; ++i) {
@@ -94,6 +100,8 @@ REGISTER_COMMAND_NAME(@"rule")
newRule.state = SNTRuleStateSilentBlacklist;
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
newRule.state = SNTRuleStateRemove;
} else if ([arg caseInsensitiveCompare:@"--check"] == NSOrderedSame) {
check = YES;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
@@ -119,6 +127,11 @@ REGISTER_COMMAND_NAME(@"rule")
}
}
if (check) {
if (!newRule.shasum) return [self printErrorUsageAndExit:@"--check requires --sha256"];
return [self printStateOfRule:newRule daemonConnection:daemonConn];
}
if (path) {
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
if (!fi.path) {
@@ -157,4 +170,58 @@ REGISTER_COMMAND_NAME(@"rule")
}];
}
+ (void)printStateOfRule:(SNTRule *)rule daemonConnection:(SNTXPCConnection *)daemonConn {
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.shasum : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.shasum : nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
__block NSMutableString *output;
[[daemonConn remoteObjectProxy] decisionForFilePath:nil
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256
reply:^(SNTEventState s) {
output = (SNTEventStateAllow & s) ? @"Whitelisted".mutableCopy : @"Blacklisted".mutableCopy;
switch (s) {
case SNTEventStateAllowUnknown:
case SNTEventStateBlockUnknown:
[output appendString:@" (Unknown)"];
break;
case SNTEventStateAllowBinary:
case SNTEventStateBlockBinary:
[output appendString:@" (Binary)"];
break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate:
[output appendString:@" (Certificate)"];
break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope:
[output appendString:@" (Scope)"];
break;
default:
output = @"None".mutableCopy;
break;
}
if (isatty(STDOUT_FILENO)) {
if ((SNTEventStateAllow & s)) {
[output insertString:@"\033[32m" atIndex:0];
[output appendString:@"\033[0m"];
} else if ((SNTEventStateBlock & s)) {
[output insertString:@"\033[31m" atIndex:0];
[output appendString:@"\033[0m"];
} else {
[output insertString:@"\033[33m" atIndex:0];
[output appendString:@"\033[0m"];
}
}
dispatch_group_leave(group);
}];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
printf("Cannot communicate with daemon");
exit(1);
}
printf("%s\n", output.UTF8String);
exit(0);
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import "SNTConfigurator.h"
@@ -102,15 +104,30 @@ REGISTER_COMMAND_NAME(@"status")
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] syncLastSuccess];
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] fullSyncLastSuccess];
NSString *lastSyncSuccessStr = [dateFormatter stringFromDate:lastSyncSuccess] ?: @"Never";
NSDate *lastRuleSyncSuccess = [[SNTConfigurator configurator] ruleSyncLastSuccess];
NSString *lastRuleSyncSuccessStr =
[dateFormatter stringFromDate:lastRuleSyncSuccess] ?: lastSyncSuccessStr;
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
__block BOOL pushNotifications;
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] pushNotifications:^(BOOL response) {
pushNotifications = response;
dispatch_group_leave(group);
}];
__block BOOL pushNotifications = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] pushNotifications:^(BOOL response) {
pushNotifications = response;
dispatch_group_leave(group);
}];
}
__block BOOL bundlesEnabled = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] bundlesEnabled:^(BOOL response) {
bundlesEnabled = response;
dispatch_group_leave(group);
}];
}
// Wait a maximum of 5s for stats collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
@@ -138,8 +155,10 @@ REGISTER_COMMAND_NAME(@"status")
@"sync" : @{
@"server" : syncURLStr ?: @"null",
@"clean_required" : @(syncCleanReqd),
@"last_successful" : lastSyncSuccessStr ?: @"null",
@"push_notifications" : pushNotifications ? @"true" : @"false"
@"last_successful_full" : lastSyncSuccessStr ?: @"null",
@"last_successful_rule" : lastRuleSyncSuccessStr ?: @"null",
@"push_notifications" : pushNotifications ? @"Connected" : @"Disconnected",
@"bundle_scanning" : @(bundlesEnabled)
},
};
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
@@ -149,23 +168,26 @@ REGISTER_COMMAND_NAME(@"status")
printf("%s\n", [statsStr UTF8String]);
} else {
printf(">>> Daemon Info\n");
printf(" %-22s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-22s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-22s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-22s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
printf(">>> Kernel Info\n");
printf(" %-22s | %lld\n", "Kernel cache count", cacheCount);
printf(" %-25s | %lld\n", "Kernel cache count", cacheCount);
printf(">>> Database Info\n");
printf(" %-22s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-22s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-22s | %lld\n", "Events Pending Upload", eventCount);
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %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"));
printf(" %-22s | %s\n", "Last Successful Sync", [lastSyncSuccessStr UTF8String]);
printf(" %-22s | %s\n", "Push Notifications", (pushNotifications ? "Yes" : "No"));
printf(" %-25s | %s\n", "Sync Server", [syncURLStr UTF8String]);
printf(" %-25s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
printf(" %-25s | %s\n", "Last Successful Full Sync", [lastSyncSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Last Successful Rule Sync", [lastRuleSyncSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Push Notifications",
(pushNotifications ? "Connected" : "Disconnected"));
printf(" %-25s | %s\n", "Bundle Scanning", (bundlesEnabled ? "Yes" : "No"));
}
}

View File

@@ -12,9 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#include <IOKit/kext/KextManager.h>
@import IOKit.kext;
#import "SNTCommonEnums.h"
#import "SNTFileInfo.h"

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
/// Category on NSData providing the option of getting zlib or gzip compressed data.
@interface NSData (Zlib)

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import "SNTCommandSyncManager.h"
@@ -59,6 +61,11 @@ REGISTER_COMMAND_NAME(@"sync")
exit(1);
}
if (![[SNTConfigurator configurator] syncBaseURL]) {
LOGE(@"Missing SyncBaseURL. Exiting.");
exit(1);
}
SNTCommandSync *s = [[self alloc] init];
[daemonConn resume];
BOOL daemon = [arguments containsObject:@"--daemon"];

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
extern NSString *const kXSRFToken;
extern NSString *const kSerialNumber;
@@ -32,6 +34,9 @@ extern NSString *const kBlacklistRegex;
extern NSString *const kBinaryRuleCount;
extern NSString *const kCertificateRuleCount;
extern NSString *const kFCMToken;
extern NSString *const kFCMFullSyncInterval;
extern NSString *const kFCMGlobalRuleSyncDeadline;
extern NSString *const kBundlesEnabled;
extern NSString *const kEvents;
extern NSString *const kFileSHA256;
@@ -54,9 +59,13 @@ extern NSString *const kLoggedInUsers;
extern NSString *const kCurrentSessions;
extern NSString *const kFileBundleID;
extern NSString *const kFileBundlePath;
extern NSString *const kFileBundleExecutableRelPath;
extern NSString *const kFileBundleName;
extern NSString *const kFileBundleVersion;
extern NSString *const kFileBundleShortVersionString;
extern NSString *const kFileBundleHash;
extern NSString *const kFileBundleHashMilliseconds;
extern NSString *const kFileBundleBinaryCount;
extern NSString *const kPID;
extern NSString *const kPPID;
extern NSString *const kParentName;
@@ -94,3 +103,17 @@ extern NSString *const kFullSync;
extern NSString *const kRuleSync;
extern NSString *const kConfigSync;
extern NSString *const kLogSync;
extern const NSUInteger kDefaultEventBatchSize;
///
/// kDefaultFullSyncInterval
/// kDefaultFCMFullSyncInterval
/// kDefaultFCMGlobalRuleSyncDeadline
///
/// Are represented in seconds
///
extern const NSUInteger kDefaultFullSyncInterval;
extern const NSUInteger kDefaultFCMFullSyncInterval;
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;

View File

@@ -34,6 +34,9 @@ NSString *const kBlacklistRegex = @"blacklist_regex";
NSString *const kBinaryRuleCount = @"binary_rule_count";
NSString *const kCertificateRuleCount = @"certificate_rule_count";
NSString *const kFCMToken = @"fcm_token";
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
NSString *const kFCMGlobalRuleSyncDeadline = @"fcm_global_rule_sync_deadline";
NSString *const kBundlesEnabled = @"bundles_enabled";
NSString *const kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -56,9 +59,13 @@ NSString *const kLoggedInUsers = @"logged_in_users";
NSString *const kCurrentSessions = @"current_sessions";
NSString *const kFileBundleID = @"file_bundle_id";
NSString *const kFileBundlePath = @"file_bundle_path";
NSString *const kFileBundleExecutableRelPath = @"file_bundle_executable_rel_path";
NSString *const kFileBundleName = @"file_bundle_name";
NSString *const kFileBundleVersion = @"file_bundle_version";
NSString *const kFileBundleShortVersionString = @"file_bundle_version_string";
NSString *const kFileBundleHash = @"file_bundle_hash";
NSString *const kFileBundleHashMilliseconds = @"file_bundle_hash_millis";
NSString *const kFileBundleBinaryCount = @"file_bundle_binary_count";
NSString *const kPID = @"pid";
NSString *const kPPID = @"ppid";
NSString *const kParentName = @"parent_name";
@@ -96,3 +103,8 @@ NSString *const kFullSync = @"full_sync";
NSString *const kRuleSync = @"rule_sync";
NSString *const kConfigSync = @"config_sync";
NSString *const kLogSync = @"log_sync";
const NSUInteger kDefaultEventBatchSize = 50;
const NSUInteger kDefaultFullSyncInterval = 600;
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;

View File

@@ -12,12 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
- (BOOL)uploadEvents:(NSArray *)events;
- (BOOL)syncBundleEvents;
@end

View File

@@ -44,28 +44,6 @@
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
- (BOOL)syncBundleEvents {
NSMutableArray *newEvents = [NSMutableArray array];
for (NSString *bundlePath in [NSSet setWithArray:self.syncState.bundleBinaryRequests]) {
__block NSArray *relatedBinaries;
__block BOOL shouldCancel = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
relatedBinaries = [self findRelatedBinaries:bundlePath shouldCancel:&shouldCancel];
dispatch_semaphore_signal(sema);
});
// Give the search up to 5m to run
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 300))) {
LOGD(@"Timed out while searching for related binaries at path %@", bundlePath);
shouldCancel = YES;
} else {
[newEvents addObjectsFromArray:relatedBinaries];
}
}
return [self uploadEvents:newEvents];
}
- (BOOL)uploadEvents:(NSArray *)events {
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
@@ -79,7 +57,7 @@
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
if (!r) return NO;
// Keep track of bundle search requests
// A list of bundle hashes that require their related binary events to be uploaded.
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
LOGI(@"Uploaded %lu events", uploadEvents.count);
@@ -122,15 +100,22 @@
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
break;
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case SNTEventStateBundleBinary: ADDKEY(newEvent, kDecision, kDecisionBundleBinary); break;
case SNTEventStateBundleBinary:
ADDKEY(newEvent, kDecision, kDecisionBundleBinary);
[newEvent removeObjectForKey:kExecutionTime];
break;
default: ADDKEY(newEvent, kDecision, kDecisionUnknown);
}
ADDKEY(newEvent, kFileBundleID, event.fileBundleID);
ADDKEY(newEvent, kFileBundlePath, event.fileBundlePath);
ADDKEY(newEvent, kFileBundleExecutableRelPath, event.fileBundleExecutableRelPath);
ADDKEY(newEvent, kFileBundleName, event.fileBundleName);
ADDKEY(newEvent, kFileBundleVersion, event.fileBundleVersion);
ADDKEY(newEvent, kFileBundleShortVersionString, event.fileBundleVersionString);
ADDKEY(newEvent, kFileBundleHash, event.fileBundleHash);
ADDKEY(newEvent, kFileBundleHashMilliseconds, event.fileBundleHashMilliseconds);
ADDKEY(newEvent, kFileBundleBinaryCount, event.fileBundleBinaryCount);
ADDKEY(newEvent, kPID, event.pid);
ADDKEY(newEvent, kPPID, event.ppid);
@@ -161,73 +146,4 @@
#undef ADDKEY
}
/**
Find binaries within a bundle given the bundle's path. Will run until completion, however long
that might be. Search is done within the bundle concurrently, using up to 25 threads at once.
@param path, the path to begin searching underneath
@param shouldCancel, if YES, the search is cancelled part way through.
@return array of SNTStoredEvent's
*/
- (NSArray *)findRelatedBinaries:(NSString *)path shouldCancel:(BOOL *)shouldCancel {
// For storing the generated events, with a simple lock for writing.
NSMutableArray *relatedEvents = [NSMutableArray array];
NSLock *relatedEventsLock = [[NSLock alloc] init];
// Limit the number of threads that can process files at once to keep CPU usage down.
dispatch_semaphore_t sema = dispatch_semaphore_create(25);
// Group the processing into a single group so we can wait on the whole group at the end.
dispatch_group_t group = dispatch_group_create();
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
while (1) {
@autoreleasepool {
if (*shouldCancel) break;
NSString *file = [dirEnum nextObject];
if (!file) break;
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
// Wait for a processing thread to become available
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^{
@autoreleasepool {
NSString *newFile = [path stringByAppendingPathComponent:file];
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:newFile];
if (!fi.isExecutable) {
dispatch_semaphore_signal(sema);
return;
}
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.filePath = fi.path;
se.fileSHA256 = fi.SHA256;
se.decision = SNTEventStateBundleBinary;
se.fileBundleID = fi.bundleIdentifier;
se.fileBundleName = fi.bundleName;
se.fileBundlePath = fi.bundlePath;
se.fileBundleVersion = fi.bundleVersion;
se.fileBundleVersionString = fi.bundleShortVersionString;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
se.signingChain = cs.certificates;
[relatedEventsLock lock];
[relatedEvents addObject:se];
[relatedEventsLock unlock];
dispatch_semaphore_signal(sema);
}
});
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return relatedEvents;
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncLogUpload : SNTCommandSyncStage

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTXPCSyncdInterface.h"
@class SNTXPCConnection;

View File

@@ -14,6 +14,8 @@
#import "SNTCommandSyncManager.h"
@import SystemConfiguration;
#import <MOLAuthenticatingURLSession.h>
#import <MOLFCMClient/MOLFCMClient.h>
@@ -26,26 +28,55 @@
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncState.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCSyncdInterface.h"
// Syncing time constants
const uint64_t kFullSyncInterval = 600;
const uint64_t kFullSyncFCMInterval = 14400;
const uint64_t kGlobalRuleSyncLeeway = 600;
static NSString *const kFCMActionKey = @"action";
static NSString *const kFCMFileHashKey = @"file_hash";
static NSString *const kFCMFileNameKey = @"file_name";
static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@interface SNTCommandSyncManager () {
SCNetworkReachabilityRef _reachability;
}
@interface SNTCommandSyncManager ()
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@property(nonatomic) NSCache *dispatchLock;
@property(nonatomic) NSCache *ruleSyncCache;
@property NSUInteger FCMFullSyncInterval;
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property MOLFCMClient *FCMClient;
@property(nonatomic) SNTXPCConnection *daemonConn;
@property BOOL targetedRuleSync;
@property(nonatomic) BOOL reachable;
@end
// Called when the network state changes
static void reachabilityHandler(
SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
// Put this check and set on the main thread to ensure serial access.
dispatch_async(dispatch_get_main_queue(), ^{
SNTCommandSyncManager *commandSyncManager = (__bridge SNTCommandSyncManager *)info;
// Only call the setter when there is a change. This will filter out the redundant calls to this
// callback whenever the network interface states change.
if (commandSyncManager.reachable != (flags & kSCNetworkReachabilityFlagsReachable)) {
commandSyncManager.reachable = (flags & kSCNetworkReachabilityFlagsReachable);
}
});
}
@implementation SNTCommandSyncManager
#pragma mark init
@@ -56,7 +87,7 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
_daemonConn = daemonConn;
_daemon = daemon;
_fullSyncTimer = [self createSyncTimerWithBlock:^{
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncFCMInterval];
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.FCMFullSyncInterval];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kFullSync];
[self preflight];
@@ -81,12 +112,44 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
}];
_dispatchLock = [[NSCache alloc] init];
_ruleSyncCache = [[NSCache alloc] init];
_eventBatchSize = kDefaultEventBatchSize;
_FCMFullSyncInterval = kDefaultFCMFullSyncInterval;
_FCMGlobalRuleSyncDeadline = kDefaultFCMGlobalRuleSyncDeadline;
}
return self;
}
#pragma mark SNTSyncdXPC protocol methods
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event reply:(void (^)(BOOL))reply {
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if (event && [p uploadEvents:@[event]]) {
BOOL needsRelatedEvents = [syncState.bundleBinaryRequests containsObject:event.fileBundleHash];
reply(needsRelatedEvents);
if (needsRelatedEvents) {
LOGD(@"Needs related events");
} else {
LOGD(@"Bundle event upload complete");
}
} else {
reply(NO);
LOGE(@"Bundle event upload failed");
}
}
- (void)postBundleEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events {
SNTCommandSyncState *syncState = [self createSyncState];
syncState.eventBatchSize = self.eventBatchSize;
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if (events && [p uploadEvents:events]) {
LOGD(@"Bundle events upload complete");
} else {
LOGE(@"Bundle events upload failed");
}
}
- (void)postEventToSyncServer:(SNTStoredEvent *)event {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc]
initWithState:[self createSyncState]];
@@ -97,10 +160,6 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
}
}
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply((self.FCMClient.FCMToken != nil));
}
@@ -134,7 +193,7 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
LOGE(@"FCM connection error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
@@ -145,56 +204,40 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSData *entryData;
NSDictionary *message = [self messageFromMessageData:[self messageDataFromFCMmessage:FCMmessage]];
// Sort through the entries in the FCM message.
for (NSDictionary *entry in FCMmessage[@"data"]) {
if ([entry[@"key"] isEqualToString:@"blob"]) {
entryData = [entry[@"value"] dataUsingEncoding:NSUTF8StringEncoding];
break;
}
}
if (!entryData) {
if (!message) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSError *error;
NSDictionary *actionMessage = [NSJSONSerialization JSONObjectWithData:entryData
options:NSJSONReadingAllowFragments
error:&error];
if (!actionMessage) {
LOGD(@"Unable to parse push notification message value: %@", error);
NSString *action = message[kFCMActionKey];
if (!action) {
LOGD(@"Push notification message contains no action");
return;
}
// Store the file name and hash in a cache. When the rule is actually added, use the cache
// to build a user notification.
NSString *fileHash = actionMessage[@"file_hash"];
NSString *fileName = actionMessage[@"file_name"];
NSString *fileHash = message[kFCMFileHashKey];
NSString *fileName = message[kFCMFileNameKey];
if (fileName && fileHash) {
[self.ruleSyncCache setObject:fileName forKey:fileHash];
}
NSString *action = actionMessage[@"action"];
if (action) {
LOGD(@"Push notification action: %@ received", action);
} else {
LOGD(@"Push notification message contains no action");
}
LOGD(@"Push notification action: %@ received", action);
if ([action isEqualToString:kFullSync]) {
[self fullSync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetMachineID = actionMessage[@"target_host_id"];
if (![targetMachineID isKindOfClass:[NSNull class]] &&
[targetMachineID.lowercaseString isEqualToString:machineID.lowercaseString]) {
NSString *targetHostID = message[kFCMTargetHostIDKey];
if (targetHostID && [targetHostID caseInsensitiveCompare:machineID] == NSOrderedSame) {
LOGD(@"Targeted rule_sync for host_id: %@", targetHostID);
self.targetedRuleSync = YES;
[self ruleSync];
} else {
uint32_t delaySeconds = arc4random_uniform(kGlobalRuleSyncLeeway);
LOGD(@"Staggering rule download, %u second delay for this machine", delaySeconds);
uint32_t delaySeconds = arc4random_uniform((uint32_t)self.FCMGlobalRuleSyncDeadline);
LOGD(@"Global rule_sync, staggering: %u second delay", delaySeconds);
[self ruleSyncSecondsFromNow:delaySeconds];
}
} else if ([action isEqualToString:kConfigSync]) {
@@ -206,6 +249,33 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
}
}
- (NSData *)messageDataFromFCMmessage:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
NSError *error;
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!rawMessage) {
LOGD(@"Unable to parse push notification message data: %@", error);
return nil;
}
// Create a new message dropping unexpected values
NSArray *allowedKeys = @[ kFCMActionKey, kFCMFileHashKey, kFCMFileNameKey, kFCMTargetHostIDKey ];
NSMutableDictionary *message = [NSMutableDictionary dictionaryWithCapacity:allowedKeys.count];
for (NSString *key in allowedKeys) {
if ([rawMessage[key] isKindOfClass:[NSString class]] && [rawMessage[key] length]) {
message[key] = rawMessage[key];
}
}
return message.count ? [message copy] : nil;
}
#pragma mark sync timer control
- (void)fullSync {
@@ -246,15 +316,21 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
if ([p sync]) {
LOGD(@"Preflight complete");
// Start listening for push notifications with a full sync every kFullSyncFCMInterval or
// revert to full syncing every kFullSyncInterval.
// Clean up reachability if it was started for a non-network error
[self stopReachability];
self.eventBatchSize = syncState.eventBatchSize;
// Start listening for push notifications with a full sync every FCMFullSyncInterval
if (syncState.daemon && syncState.FCMToken) {
self.FCMFullSyncInterval = syncState.FCMFullSyncInterval;
self.FCMGlobalRuleSyncDeadline = syncState.FCMGlobalRuleSyncDeadline;
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCMToken not provided. Sync every %llu min.", kFullSyncInterval / 60);
LOGD(@"FCMToken not provided. Sync every %lu min.", kDefaultFullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
}
if (syncState.uploadLogURL) {
@@ -263,8 +339,13 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
return [self eventUploadWithSyncState:syncState];
}
} else {
LOGE(@"Preflight failed, aborting run");
if (!syncState.daemon) exit(1);
if (!syncState.daemon) {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
}
@@ -293,9 +374,6 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
if (syncState.bundleBinaryRequests.count) {
return [self eventUploadBundleBinariesWithSyncState:syncState];
}
return [self postflightWithSyncState:syncState];
} else {
LOGE(@"Rule download failed, aborting run");
@@ -303,16 +381,6 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
}
}
- (void)eventUploadBundleBinariesWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p syncBundleEvents]) {
LOGD(@"Event upload for bundle binaries complete");
} else {
LOGW(@"Event upload for bundle binary search failed");
}
return [self postflightWithSyncState:syncState];
}
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
@@ -361,8 +429,11 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
LOGW(@"Missing Machine Owner.");
}
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
syncState.xsrfToken = token;
dispatch_group_leave(group);
}];
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
@@ -397,7 +468,8 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return syncState;
}
@@ -413,4 +485,39 @@ const uint64_t kGlobalRuleSyncLeeway = 600;
return ([self.dispatchLock objectForKey:action] == nil);
}
#pragma mark reachability methods
- (void)setReachable:(BOOL)reachable {
_reachable = reachable;
if (reachable) {
[self stopReachability];
[self fullSync];
}
}
// Start listening for network state changes on a background thread
- (void)startReachability {
if (_reachability) return;
const char *nodename = [[SNTConfigurator configurator] syncBaseURL].host.UTF8String;
_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, nodename);
SCNetworkReachabilityContext context = {
.info = (__bridge void *)self
};
if (SCNetworkReachabilitySetCallback(_reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
_reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
} else {
[self stopReachability];
}
}
// Stop listening for network state changes
- (void)stopReachability {
if (_reachability) {
SCNetworkReachabilitySetDispatchQueue(_reachability, NULL);
if (_reachability) CFRelease(_reachability);
_reachability = NULL;
}
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncPostflight : SNTCommandSyncStage

View File

@@ -29,7 +29,7 @@
}
- (BOOL)sync {
NSDictionary *r = [self performRequest:[self requestWithDictionary:nil]];
[self performRequest:[self requestWithDictionary:nil]];
dispatch_group_t group = dispatch_group_create();
void (^replyBlock)() = ^{
@@ -43,14 +43,6 @@
reply:replyBlock];
}
// Update backoff interval
NSString *backoffInterval = r[kBackoffInterval];
if (backoffInterval) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue]
reply:replyBlock];
}
// Remove clean sync flag if we did a clean sync
if (self.syncState.cleanSync) {
dispatch_group_enter(group);

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncPreflight : SNTCommandSyncStage

View File

@@ -74,9 +74,21 @@
if (!resp) return NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setBundlesEnabled:[resp[kBundlesEnabled] boolValue] reply:^{
dispatch_group_leave(group);
}];
self.syncState.eventBatchSize = [resp[kBatchSize] unsignedIntegerValue] ?: kDefaultEventBatchSize;
self.syncState.FCMToken = resp[kFCMToken];
self.syncState.eventBatchSize = [resp[kBatchSize] intValue] ?: 50;
// Don't let these go too low
NSUInteger value = [resp[kFCMFullSyncInterval] unsignedIntegerValue];
self.syncState.FCMFullSyncInterval =
(value < kDefaultFullSyncInterval) ? kDefaultFCMFullSyncInterval : value;
value = [resp[kFCMGlobalRuleSyncDeadline] unsignedIntegerValue];
self.syncState.FCMGlobalRuleSyncDeadline =
(value < 60) ? kDefaultFCMGlobalRuleSyncDeadline : value;
self.syncState.uploadLogURL = [NSURL URLWithString:resp[kUploadLogsURL]];
@@ -99,6 +111,7 @@
self.syncState.cleanSync = YES;
}
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
return YES;
}

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncRuleDownload : SNTCommandSyncStage

View File

@@ -67,18 +67,24 @@
return NO;
}
sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] setRuleSyncLastSuccess:[NSDate date] reply:^{
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
LOGI(@"Added %lu rules", self.syncState.downloadedRules.count);
if (self.syncState.targetedRuleSync) {
NSString *fileName;
for (SNTRule *r in self.syncState.downloadedRules) {
fileName = [[self.syncState.ruleSyncCache objectForKey:r.shasum] copy];
NSString *fileName = [[self.syncState.ruleSyncCache objectForKey:r.shasum] copy];
[self.syncState.ruleSyncCache removeObjectForKey:r.shasum];
if (fileName) break;
if (fileName.length) {
NSString *message = [NSString stringWithFormat:@"%@ can now be run", fileName];
[[self.daemonConn remoteObjectProxy]
postRuleSyncNotificationWithCustomMessage:message reply:^{}];
}
}
NSString *message = fileName ? [NSString stringWithFormat:@"%@ can now be run", fileName] : nil;
[[self.daemonConn remoteObjectProxy]
postRuleSyncNotificationWithCustomMessage:message reply:^{}];
}
return YES;

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
@class SNTCommandSyncState;
@class SNTXPCConnection;

View File

@@ -12,8 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
@class SNTCommandSyncManager;
@class SNTXPCConnection;
/// An instance of this class is passed to each stage of the sync process for storing data
@@ -32,9 +35,15 @@
/// An XSRF token to send in the headers with each request.
@property NSString *xsrfToken;
/// A FCM token to subscribe to push notifications.
/// An FCM token to subscribe to push notifications.
@property(copy) NSString *FCMToken;
/// Full sync interval in seconds while listening for FCM messages.
@property NSUInteger FCMFullSyncInterval;
/// Leeway time in seconds when receiving a global rule sync message.
@property NSUInteger FCMGlobalRuleSyncDeadline;
/// Machine identifier and owner.
@property(copy) NSString *machineID;
@property(copy) NSString *machineOwner;
@@ -53,7 +62,7 @@
/// Log upload URL sent from server. If set, LogUpload phase needs to happen.
@property NSURL *uploadLogURL;
/// Array of bundle paths to find binaries for.
/// Array of bundle IDs to find binaries for.
@property NSArray *bundleBinaryRequests;
/// Rules downloaded from server.

View File

@@ -1,3 +0,0 @@
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
@class SNTXPCConnection;
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
// This is imported in the header rather than implementation to save
// classes that use this one from also having to import FMDB stuff.
#import <FMDB/FMDB.h>

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTDatabaseTable.h"
@class SNTNotificationMessage;
@@ -30,6 +32,14 @@
///
- (BOOL)addStoredEvent:(SNTStoredEvent *)event;
///
/// Add events to the database.
///
/// @param events the events to store.
/// @return YES if events were successfully stored.
///
- (BOOL)addStoredEvents:(NSArray<SNTStoredEvent *> *)events;
///
/// Retrieves all events in the database
///

View File

@@ -83,24 +83,37 @@
#pragma mark Loading / Storing
- (BOOL)addStoredEvent:(SNTStoredEvent *)event {
if (!event.idx ||
!event.fileSHA256 ||
!event.filePath ||
!event.occurrenceDate ||
!event.decision) return NO;
return [self addStoredEvents:@[event]];
}
NSData *eventData;
@try {
eventData = [NSKeyedArchiver archivedDataWithRootObject:event];
} @catch (NSException *exception) {
return NO;
- (BOOL)addStoredEvents:(NSArray<SNTStoredEvent *> *)events {
NSMutableDictionary *eventsData = [NSMutableDictionary dictionaryWithCapacity:events.count];
for (SNTStoredEvent *event in events) {
if (!event.idx ||
!event.fileSHA256 ||
!event.filePath ||
!event.occurrenceDate ||
!event.decision) continue;
NSData *eventData;
@try {
eventData = [NSKeyedArchiver archivedDataWithRootObject:event];
} @catch (NSException *exception) {
continue;
}
eventsData[eventData] = event;
}
__block BOOL success = NO;
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
success = [db executeUpdate:@"INSERT INTO 'events' (idx, filesha256, eventdata)"
@"VALUES (?, ?, ?)",
event.idx, event.fileSHA256, eventData];
[eventsData enumerateKeysAndObjectsUsingBlock:^(NSData * eventData,
SNTStoredEvent * event,
BOOL *stop) {
success = [db executeUpdate:@"INSERT INTO 'events' (idx, filesha256, eventdata)"
@"VALUES (?, ?, ?)",
event.idx, event.fileSHA256, eventData];
if (!success) *stop = YES;
}];
}];
return success;

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
#import "SNTDatabaseTable.h"

View File

@@ -1,3 +0,0 @@
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// The main controller class for santad
///

View File

@@ -14,7 +14,8 @@
#import "SNTApplication.h"
#import <DiskArbitration/DiskArbitration.h>
@import DiskArbitration;
#include <sys/stat.h>
#include <sys/types.h>

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTXPCControlInterface.h"
@class SNTDriverManager;

View File

@@ -24,7 +24,10 @@
#import "SNTPolicyProcessor.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
#import "SNTStoredEvent.h"
#import "SNTStrengthify.h"
#import "SNTSyncdQueue.h"
#import "SNTXPCBundleServiceInterface.h"
#import "SNTXPCConnection.h"
#import "SNTXPCNotifierInterface.h"
#import "SNTXPCSyncdInterface.h"
@@ -112,15 +115,19 @@ double watchdogRAMPeak = 0;
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
signingCertificate:(MOLCertificate *)signingCertificate
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply {
reply([self.policyProcessor decisionForFilePath:filePath
fileSHA256:fileSHA256
signingCertificate:signingCertificate].decision);
certificateSHA256:certificateSHA256].decision);
}
#pragma mark Config Ops
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply {
reply(watchdogCPUEvents, watchdogRAMEvents, watchdogCPUPeak, watchdogRAMPeak);
}
- (void)clientMode:(void (^)(SNTClientMode))reply {
reply([[SNTConfigurator configurator] clientMode]);
}
@@ -143,7 +150,12 @@ double watchdogRAMPeak = 0;
}
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setSyncLastSuccess:date];
[[SNTConfigurator configurator] setFullSyncLastSuccess:date];
reply();
}
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setRuleSyncLastSuccess:date];
reply();
}
@@ -157,6 +169,8 @@ double watchdogRAMPeak = 0;
options:0
error:NULL];
[[SNTConfigurator configurator] setWhitelistPathRegex:re];
LOGI(@"Received new whitelist regex, flushing cache");
[self.driverManager flushCache];
reply();
}
@@ -165,11 +179,18 @@ double watchdogRAMPeak = 0;
options:0
error:NULL];
[[SNTConfigurator configurator] setBlacklistPathRegex:re];
LOGI(@"Received new blacklist regex, flushing cache");
[self.driverManager flushCache];
reply();
}
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply {
reply(watchdogCPUEvents, watchdogRAMEvents, watchdogCPUPeak, watchdogRAMPeak);
- (void)bundlesEnabled:(void (^)(BOOL))reply {
reply([SNTConfigurator configurator].bundlesEnabled);
}
- (void)setBundlesEnabled:(BOOL)bundlesEnabled reply:(void (^)())reply {
[[SNTConfigurator configurator] setBundlesEnabled:bundlesEnabled];
reply();
}
#pragma mark GUI Ops
@@ -181,13 +202,23 @@ double watchdogRAMPeak = 0;
self.notQueue.notifierConnection = c;
}
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *bs = [[SNTXPCConnection alloc] initClientWithServiceName:@"com.google.santabs"];
bs.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
[bs resume];
[[bs remoteObjectProxy] setBundleNotificationListener:listener];
}
#pragma mark syncd Ops
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener {
// Only allow one active syncd connection
if (self.syncdQueue.syncdConnection) return;
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCSyncdInterface syncdInterface];
c.invalidationHandler = ^{
[self.syncdQueue stopSyncingEvents];
self.syncdQueue.syncdConnection = nil;
self.syncdQueue.invalidationHandler();
};
c.acceptedHandler = ^{
@@ -197,12 +228,6 @@ double watchdogRAMPeak = 0;
self.syncdQueue.syncdConnection = c;
}
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
[[self.syncdQueue.syncdConnection remoteObjectProxy] rescheduleSyncSecondsFromNow:seconds];
[[SNTConfigurator configurator] setSyncBackOff:YES];
reply();
}
- (void)pushNotifications:(void (^)(BOOL))reply {
[self.syncdQueue.syncdConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
reply(response);
@@ -215,4 +240,57 @@ double watchdogRAMPeak = 0;
reply();
}
#pragma mark Bundle ops
///
/// This method is only used for santactl's bundleinfo command. For blocked executions, SantaGUI
/// calls on santabs directly.
///
/// Hash a bundle for an event. The SNTBundleHashBlock will be called with nil parameters if a
/// failure or cancellation occurs.
///
/// @param event The event that includes the fileBundlePath to be hashed.
/// @param reply A SNTBundleHashBlock to be executed upon completion or cancellation.
///
/// @note If there is a current NSProgress when called this method will report back it's progress.
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
reply:(SNTBundleHashBlock)reply {
SNTXPCConnection *bs =
[[SNTXPCConnection alloc] initClientWithServiceName:[SNTXPCBundleServiceInterface serviceId]];
bs.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
[bs resume];
[[bs remoteObjectProxy] hashBundleBinariesForEvent:event reply:reply];
}
///
/// Used by SantaGUI sync the offending event and potentially all the related events,
/// if the sync server has not seen them before.
///
/// @param event The offending event, fileBundleHash & fileBundleBinaryCount need to be populated.
/// @param relatedEvents Nexted bundle events.
///
- (void)syncBundleEvent:(SNTStoredEvent *)event
relatedEvents:(NSArray<SNTStoredEvent *> *)events {
SNTEventTable *eventTable = [SNTDatabaseController eventTable];
// Delete the event cached by the execution controller.
[eventTable deleteEventWithId:event.idx];
// Add the updated event.
[eventTable addStoredEvent:event];
WEAKIFY(self);
// Sync the updated event. If the sync server needs the related events, add them to the eventTable
// and upload them too.
[self.syncdQueue addBundleEvent:event reply:^(BOOL needRelatedEvents) {
STRONGIFY(self);
if (needRelatedEvents) {
[eventTable addStoredEvents:events];
[self.syncdQueue addBundleEvents:events];
}
}];
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
// This is imported in the header rather than implementation to saves
// classes that use this one from also having to import FMDB stuff.
#import <FMDB/FMDB.h>

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#include "SNTKernelCommon.h"
@class SNTNotificationMessage;

View File

@@ -14,8 +14,9 @@
#import "SNTDriverManager.h"
#import <IOKit/IODataQueueClient.h>
#import <IOKit/Kext/KextManager.h>
@import IOKit.kext;
#include <mach/mach_port.h>
#import "SNTLogging.h"

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTKernelCommon.h"
@class SNTCachedDecision;

View File

@@ -34,6 +34,8 @@
// Caches for uid->username and gid->groupname lookups.
@property NSCache<NSNumber *, NSString *> *userNameMap;
@property NSCache<NSNumber *, NSString *> *groupNameMap;
@property NSDateFormatter *dateFormatter;
@end
@implementation SNTEventLog
@@ -49,6 +51,10 @@
_userNameMap.countLimit = 100;
_groupNameMap = [[NSCache alloc] init];
_groupNameMap.countLimit = 100;
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
_dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
}
return self;
}
@@ -226,7 +232,14 @@
diskProperties[@"DADeviceModel"] ?: @""];
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
LOGI(@"action=DISKAPPEAR|mount=%@|volume=%@|bsdname=%@|fs=%@|model=%@|serial=%@|bus=%@|dmgpath=%@",
double appearance = [diskProperties[@"DAAppearanceTime"] doubleValue];
NSString *appearanceDateString =
[_dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:appearance]];
NSString *log =
@"action=DISKAPPEAR|mount=%@|volume=%@|bsdname=%@|fs=%@|"
@"model=%@|serial=%@|bus=%@|dmgpath=%@|appearance=%@";
LOGI(log,
[diskProperties[@"DAVolumePath"] path] ?: @"",
diskProperties[@"DAVolumeName"] ?: @"",
diskProperties[@"DAMediaBSDName"] ?: @"",
@@ -234,7 +247,8 @@
model ?: @"",
serial,
diskProperties[@"DADeviceProtocol"] ?: @"",
dmgPath);
dmgPath,
appearanceDateString);
}
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
#include "SNTKernelCommon.h"

View File

@@ -115,7 +115,8 @@
// Actually make the decision.
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo
fileSHA256:nil
signingCertificate:csInfo.leafCertificate];
certificateSHA256:csInfo.leafCertificate.SHA256];
cd.certCommonName = csInfo.leafCertificate.commonName;
cd.vnodeId = message.vnode_id;
// Formulate an action from the decision
@@ -176,11 +177,19 @@
if (action != ACTION_RESPOND_ALLOW) {
[_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.
dispatch_async(_eventQueue, ^{
[self initiateEventUploadForEvent:se];
});
if ([[SNTConfigurator configurator] bundlesEnabled] && binInfo.bundle) {
// If the binary is apart of a bundle, find and hash all the related binaries in the bundle.
// Let the GUI know hashing is needed. Once the hashing is complete the GUI will send a
// message to santad to perform the upload logic for bundles.
// See syncBundleEvent:relatedEvents: for more info.
se.needsBundleHash = YES;
} else if ([[SNTConfigurator configurator] syncBaseURL]) {
// 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.
dispatch_async(_eventQueue, ^{
[_syncdQueue addEvent:se];
});
}
if (!cd.silentBlock) {
// Let the user know what happened, both on the terminal and in the GUI.
@@ -245,24 +254,6 @@
return NO;
}
/**
This sends the event that was just saved to santactl for immediate upload, so that the user
has something to vote in straight away.
This method is always called on a serial queue 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.
// 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;
[self.syncdQueue addEvent:event];
}
- (void)printMessage:(NSString *)msg toTTYForPID:(pid_t)pid {
if (pid < 2) return; // don't bother even looking for launchd.

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
@class SNTStoredEvent;
@class SNTXPCConnection;

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
@@ -37,22 +39,24 @@
/// @param fileInfo A SNTFileInfo object.
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
/// be calculated by this method from the filePath.
/// @param signingCertificate A MOLCertificate object, can be nil.
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
/// @param certificateSHA256 A SHA256 hash of the signing certificate, can be nil.
/// @note If fileSHA256 and certificateSHA256 are both passed in, the most specific rule will be
/// returned. Binary rules take precedence over cert rules.
/// @note This method can also be used to generate a SNTCachedDecision object without any
/// artifacts on disk. Simply pass nil to fileInfo and pass in the desired SHA256s.
///
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nullable SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256
signingCertificate:(nullable MOLCertificate *)signingCertificate;
certificateSHA256:(nullable NSString *)certificateSHA256;
///
/// A wrapper for decisionForFileInfo:fileSHA256:signingCertificate:. This method is slower as it
/// A wrapper for decisionForFileInfo:fileSHA256:certificateSHA256:. This method is slower as it
/// has to create the SNTFileInfo object. This is mainly used by the santactl binary because
/// SNTFileInfo is not SecureCoding compliant. If the SHA256 hash of the file has already been
/// calculated, use the fileSHA256 parameter to save a second calculation of the hash.
///
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
- (nonnull SNTCachedDecision *)decisionForFilePath:(nullable NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256
signingCertificate:(nullable MOLCertificate *)signingCertificate;
certificateSHA256:(nullable NSString *)certificateSHA256;
@end

View File

@@ -38,14 +38,11 @@
- (SNTCachedDecision *)decisionForFileInfo:(SNTFileInfo *)fileInfo
fileSHA256:(NSString *)fileSHA256
signingCertificate:(MOLCertificate *)signingCertificate {
certificateSHA256:(NSString *)certificateSHA256 {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
cd.certSHA256 = certificateSHA256;
cd.quarantineURL = fileInfo.quarantineDataURL;
if (signingCertificate) {
cd.certCommonName = signingCertificate.commonName;
cd.certSHA256 = signingCertificate.SHA256;
}
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256
certificateSHA256:cd.certSHA256];
@@ -113,13 +110,16 @@
- (SNTCachedDecision *)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
signingCertificate:(MOLCertificate *)signingCertificate {
NSError *error;
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
certificateSHA256:(NSString *)certificateSHA256 {
SNTFileInfo *fileInfo;
if (filePath) {
NSError *error;
fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
}
return [self decisionForFileInfo:fileInfo
fileSHA256:fileSHA256
signingCertificate:signingCertificate];
certificateSHA256:certificateSHA256];
}
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
@class SNTStoredEvent;
@class SNTXPCConnection;
@@ -22,6 +24,8 @@
@property(copy) void (^acceptedHandler)();
- (void)addEvent:(SNTStoredEvent *)event;
- (void)addBundleEvents:(NSArray<SNTStoredEvent *> *)events;
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(BOOL))reply;
- (void)startSyncingEvents;
- (void)stopSyncingEvents;

View File

@@ -38,27 +38,31 @@
return self;
}
- (void)addBundleEvents:(NSArray<SNTStoredEvent *> *)events {
if (![self backoffForBundleHash:events.firstObject.fileBundleHash]) return;
[self dispatchBlockOnSyncdQueue:^{
[self.syncdConnection.remoteObjectProxy postBundleEventsToSyncServer:events];
}];
}
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(BOOL))reply {
if (![self backoffForBundleHash:event.fileBundleHash]) return;
[self dispatchBlockOnSyncdQueue:^{
[self.syncdConnection.remoteObjectProxy postBundleEventToSyncServer:event
reply:^(BOOL needRelatedEvents) {
// Remove the backoff entry for the inital block event. The same event will be included in the
// related events synced using addBundleEvents:.
if (needRelatedEvents) [self.uploadBackoff removeObjectForKey:event.fileBundleHash];
reply(needRelatedEvents);
}];
}];
}
- (void)addEvent:(SNTStoredEvent *)event {
// The event upload is skipped if an event upload has been initiated for it in the
// last 10 minutes.
NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256];
NSDate *now = [NSDate date];
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
[self.uploadBackoff setObject:now forKey:event.fileSHA256];
// Hold events for a few seconds to allow santad and santactl to establish connections.
// If the connections are not established in time drop the event from the queue.
// They will be uploaded during a full sync.
dispatch_async(self.syncdQueue, ^{
if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
[self.syncdConnection.remoteObjectProxy postEventToSyncServer:event];
// Let em flow
dispatch_semaphore_signal(self.sema);
} else {
LOGI(@"Dropping event %@ from com.google.syncd_queue", event.fileSHA256);
}
});
if (![self backoffForEvent:event]) return;
[self dispatchBlockOnSyncdQueue:^{
[self.syncdConnection.remoteObjectProxy postEventToSyncServer:event];
}];
}
- (void)startSyncingEvents {
@@ -69,4 +73,38 @@
self.sema = dispatch_semaphore_create(0);
}
// Hold events for a few seconds to allow santad and santactl to establish connections.
// If the connections are not established in time drop the event from the queue.
// They will be uploaded during a full sync.
- (void)dispatchBlockOnSyncdQueue:(void (^)())block {
dispatch_async(self.syncdQueue, ^{
if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
if (block) block();
dispatch_semaphore_signal(self.sema);
} else {
LOGD(@"Dropping block %@ from com.google.syncd_queue", block);
}
});
}
// The event upload is skipped if an event upload has been initiated for it in the
// last 10 minutes.
- (BOOL)backoffForEvent:(SNTStoredEvent *)event {
NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256];
NSDate *now = [NSDate date];
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return NO;
[self.uploadBackoff setObject:now forKey:event.fileSHA256];
return YES;
}
// The bundle event upload is skipped if a bundle event has been initiated for it in the
// last 10 minutes.
- (BOOL)backoffForBundleHash:(NSString *)bundleHash {
NSDate *backoff = [self.uploadBackoff objectForKey:bundleHash];
NSDate *now = [NSDate date];
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return NO;
[self.uploadBackoff setObject:now forKey:bundleHash];
return YES;
}
@end

Some files were not shown because too many files have changed in this diff Show More