Compare commits

...

19 Commits

Author SHA1 Message Date
Tom Burgin
d0ede18bf4 MOLCertificate --> 1.9 (#290) 2018-07-06 12:56:15 -04:00
Alessandro Gario
6d223aea03 Various fixes (documentation, and an additional check on the JSON received from the syncserver) (#288)
* santa-driver: Fix documentation warnings
* SantaCache: Fix documentation warnings
* santactl: Always make sure that the syncserver JSON is a dictionary
2018-07-06 09:42:22 -04:00
Alessandro Gario
f7986b0a05 Update MOLXPCConnection; add support for unprivileged XPC interfaces (#287)
* Update MOLXPCConnection; add support for unprivileged XPC interfaces

* Code review changes
2018-07-05 17:20:49 -04:00
Alessandro Gario
629e70287c Add CMake support, implement fuzzers (#284)
The new CMake project allows the user to select which SDK version
to use. The Xcode path is also configurable to support non-standard
installation paths and/or systems with multiple versions installed.

Code signing can now be configured via command line, using the
CODESIGN_IDENTITY environment variable.

New fuzzing targets (libFuzzer)
 - SantaCache
 - santactl
 - santad

New make targets:
 - tests: Runs the tests
 - fuzz: Runs the fuzzer
 - redist: Regenerates the redistributable folder
 - install: Installs Santa
2018-06-29 14:15:16 -04:00
Russell Hancox
3c2a88144c santad: Wait for driver appearance using IOKit notifications. (#278)
Continue loading without driver, report status in santactl.
2018-06-12 16:15:41 -04:00
dgw
3651f18566 readme: s/precendence/precedence/ (#283) 2018-06-12 15:01:42 -04:00
Russell Hancox
472fea75b1 KernelTests: Simplify kernel tests (#282)
This change does 2 major things:

1) Makes the test runnable from within Xcode, unloading any running
santad and santa-driver, loading the just-built driver from the same folder and
then running each test.

2) Makes each test responsible for declaring what should happen to
incoming requests from the driver, instead of keeping all of that
code near the top of the file. This makes each test much clearer in what
should be happening.
2018-06-12 09:53:20 -04:00
Russell Hancox
e1b5438865 santa-driver: Re-factor some destruction methods (#281) 2018-06-11 12:54:29 -04:00
Russell Hancox
fbbf523333 santa-driver: Stop catching hasdirtyblks, the loader handles this (#280) 2018-06-06 18:56:00 -04:00
Russell Hancox
15fa53d744 santa-driver: Switch to a struct for vnode IDs, holding both the file… (#276)
santa-driver: Switch to a struct for vnode IDs, holding both the filesystem ID and vnode ID.

Also drop the separate caches for root/non-root as this doesn't offer any benefit anymore.
2018-06-05 06:43:49 -04:00
Russell Hancox
9595f80fde santad: Don't get code signature info for non Mach-O's. (#277) 2018-06-05 06:43:11 -04:00
Russell Hancox
61a67e45c1 SantaCache: Add command to print histogram of bucket distribution (#275)
* SantaCache: Add santactl command to print histogram of bucket distribution.

This currently only prints the distribution of the non-root cache. In the near future I'll unify the caches again which stops this being a problem.
2018-06-01 17:02:39 -04:00
Russell Hancox
143e690dab SantaCache: Add very basic distribution test (#273)
* SantaCache: Add very basic distribution test
2018-06-01 13:11:24 -04:00
Russell Hancox
ebd507f143 Project: Update cocoapods, again (#274) 2018-06-01 12:01:21 -04:00
Russell Hancox
f71bc0a8f7 santa-driver: Fix cache CAS operations, which haven't been working (#272)
* santa-driver: Fix cache CAS operations, which haven't really been working.
2018-06-01 11:38:25 -04:00
Russell Hancox
edc0c72464 SantaCache: Templatize key types (#271) 2018-05-30 15:50:23 -04:00
Tom Burgin
c3ce4f718b Update configuration.md (#270) 2018-05-30 11:06:45 -04:00
Tom Burgin
40ee482973 Update SNTSyslogEventLog.m (#269)
remove extra space
2018-05-29 16:39:18 -04:00
Mitchell Grenier
a5d2e6fdd2 Optional MachineID for Logs (#256)
* First draft, no UUID caching

* Cache UUID in SNTEventLog super class

* Add a configuration flag for UUID decoration

* Port from UUID to MachineID

* KVO complicance

* Remove extra newline I accidentally introduced
2018-05-29 16:16:21 -04:00
79 changed files with 3033 additions and 895 deletions

133
CMakeLists.txt Normal file
View File

@@ -0,0 +1,133 @@
cmake_minimum_required(VERSION 3.10.0)
execute_process(
COMMAND git fetch --tags --all
COMMAND git describe --tags --always --abbrev=0
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE SANTA_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND xcode-select -p
OUTPUT_VARIABLE DEFAULT_XCODE_ROOT_FOLDER
)
string(STRIP "${DEFAULT_XCODE_ROOT_FOLDER}" DEFAULT_XCODE_ROOT_FOLDER)
add_custom_target(tests)
function(AddTestTarget target_name run_as_root)
if(run_as_root)
add_custom_target(
"${target_name}_runner"
COMMAND sudo $<TARGET_FILE:${target_name}>
COMMENT "Running ${target_name}_runner as root"
)
else()
add_custom_target(
"${target_name}_runner"
COMMAND $<TARGET_FILE:${target_name}>
)
endif()
add_dependencies(tests "${target_name}_runner")
endfunction()
add_custom_target(fuzz)
function(AddFuzzTarget target_name max_len max_total_time run_as_root)
add_custom_command(TARGET "${target_name}" POST_BUILD
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" $<TARGET_FILE:${target_name}>
COMMENT "Signing ${target_name} with the following identity: ${CODESIGN_IDENTITY}"
)
if(${run_as_root})
set(sudo_command "sudo")
else()
set(sudo_command "")
endif()
add_custom_target(
"${target_name}_runner"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_corpus"
COMMAND ${sudo_command} $<TARGET_FILE:${target_name}> -max_len=${max_len} -max_total_time=${max_total_time} "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_corpus" "${CMAKE_CURRENT_SOURCE_DIR}/${target_name}_seed_corpus"
COMMENT "Running fuzzer: ${target_name}"
)
add_dependencies("${target_name}_runner" "${target_name}")
add_dependencies(fuzz "${target_name}_runner")
endfunction()
function(main)
if("${CMAKE_BUILD_TYPE}" STREQUAL "")
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
if(NOT DEFINED CODESIGN_IDENTITY)
if ("$ENV{CODESIGN_IDENTITY}" STREQUAL "")
message(FATAL_ERROR "Please define CODESIGN_IDENTITY - You can either use an environment variable or pass it to CMake with -DCODESIGN_IDENTITY=identity. If you are using sudo, make sure to keep the variable defined")
endif()
set(CODESIGN_IDENTITY "$ENV{CODESIGN_IDENTITY}")
endif()
if(NOT MACOSX_VERSION_MIN)
set(MACOSX_VERSION_MIN 10.9)
endif()
if(NOT MACOSX_SDK_VERSION)
set(sdk_version_name "Latest")
else()
set(sdk_version_name "${MACOSX_SDK_VERSION}")
endif()
if(NOT XCODE_ROOT_FOLDER)
set(XCODE_ROOT_FOLDER "${DEFAULT_XCODE_ROOT_FOLDER}")
endif()
message(STATUS "MACOSX_VERSION_MIN: ${MACOSX_VERSION_MIN}")
message(STATUS "MACOSX_SDK_VERSION: ${sdk_version_name}")
message(STATUS "XCODE_ROOT_FOLDER: ${XCODE_ROOT_FOLDER}")
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
message(STATUS "SANTA_VERSION: ${SANTA_VERSION}")
add_subdirectory(CocoaPods)
add_subdirectory(Source/santad)
add_subdirectory(Source/santactl)
add_subdirectory(Source/santabs)
add_subdirectory(Source/SantaGUI)
add_subdirectory(Source/santa-driver)
add_subdirectory(Fuzzing)
add_subdirectory(Tests)
set(redist_folder_path "${CMAKE_BINARY_DIR}/redist")
add_custom_command(
OUTPUT "${redist_folder_path}/conf/install.sh"
# Copy the binaries
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_BINARY_DIR}/Source/SantaGUI/Santa.app" "${redist_folder_path}/binaries/Santa.app"
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_BINARY_DIR}/Source/santa-driver/santa-driver.kext" "${redist_folder_path}/binaries/santa-driver.kext"
# Copy the configuration files
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/Conf" "${redist_folder_path}/conf"
COMMAND "${CMAKE_COMMAND}" -E remove_directory "${redist_folder_path}/conf/Package"
COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/Docs/deployment/com.google.santa.example.mobileconfig" "${redist_folder_path}/conf"
COMMENT "Generating redistributable package"
)
add_custom_target(redist DEPENDS "${redist_folder_path}/conf/install.sh")
add_dependencies(redist santad santa-driver santactl santabs Santa)
install(FILES Conf/com.google.santad.plist DESTINATION /Library/LaunchDaemons)
install(FILES Conf/com.google.santagui.plist DESTINATION /Library/LaunchAgents)
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E echo To finalize the installation, run the following commands)")
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E echo sudo /sbin/kextload /Library/Extensions/santa-driver.kext)")
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E echo sudo /bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist)")
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E echo sudo /bin/launchctl asuser `/usr/bin/stat -f '%u' /dev/console` /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist)")
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E echo Configuration guide available on ReadTheDocs - https://santa.readthedocs.io/en/latest/deployment/configuration/)")
endfunction()
main()

195
CocoaPods/CMakeLists.txt Normal file
View File

@@ -0,0 +1,195 @@
cmake_minimum_required(VERSION 3.10.0)
project(CocoaPods)
function(main)
add_custom_command(
OUTPUT "${CMAKE_SOURCE_DIR}/Pods/Manifest.lock"
COMMAND pod repo update && pod install
DEPENDS "${CMAKE_SOURCE_DIR}/Podfile"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Fetching CocoaPods dependencies"
VERBATIM
)
add_custom_target("${PROJECT_NAME}"
DEPENDS "${CMAKE_SOURCE_DIR}/Pods/Manifest.lock"
)
ImportCocoaPodsLibrary(
FMDB
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabase.h"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMResultSet.h"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabasePool.h"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabaseQueue.h"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDB.h"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabaseAdditions.h"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabaseQueue.m"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabaseAdditions.m"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabase.m"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMDatabasePool.m"
"${CMAKE_SOURCE_DIR}/Pods/FMDB/src/fmdb/FMResultSet.m"
)
target_link_libraries(FMDB PUBLIC sqlite3 "-framework Cocoa")
ImportCocoaPodsLibrary(
MOLCertificate
"${CMAKE_SOURCE_DIR}/Pods/MOLCertificate/Source/MOLCertificate"
"${CMAKE_SOURCE_DIR}/Pods/MOLCertificate/Source/MOLCertificate/MOLCertificate.m"
"${CMAKE_SOURCE_DIR}/Pods/MOLCertificate/Source/MOLCertificate/MOLCertificate.h"
)
target_link_libraries(MOLCertificate PUBLIC "-framework Security" "-framework Cocoa")
ImportCocoaPodsLibrary(
MOLCodesignChecker
"${CMAKE_SOURCE_DIR}/Pods/MOLCodesignChecker/Source/MOLCodesignChecker"
"${CMAKE_SOURCE_DIR}/Pods/MOLCodesignChecker/Source/MOLCodesignChecker/MOLCodesignChecker.h"
"${CMAKE_SOURCE_DIR}/Pods/MOLCodesignChecker/Source/MOLCodesignChecker/MOLCodesignChecker.m"
)
target_link_libraries(MOLCodesignChecker PUBLIC "-framework Security" "-framework Cocoa")
ImportCocoaPodsLibrary(
MOLAuthenticatingURLSession
"${CMAKE_SOURCE_DIR}/Pods/MOLAuthenticatingURLSession/Source/MOLAuthenticatingURLSession"
"${CMAKE_SOURCE_DIR}/Pods/MOLAuthenticatingURLSession/Source/MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.m"
"${CMAKE_SOURCE_DIR}/Pods/MOLAuthenticatingURLSession/Source/MOLAuthenticatingURLSession/MOLDERDecoder.m"
"${CMAKE_SOURCE_DIR}/Pods/MOLAuthenticatingURLSession/Source/MOLAuthenticatingURLSession/MOLDERDecoder.h"
"${CMAKE_SOURCE_DIR}/Pods/MOLAuthenticatingURLSession/Source/MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h"
)
target_link_libraries(MOLAuthenticatingURLSession PUBLIC "-framework Security" MOLCertificate "-framework Cocoa")
ImportCocoaPodsLibrary(
MOLFCMClient
"${CMAKE_SOURCE_DIR}/Pods/MOLFCMClient/MOLFCMClient"
"${CMAKE_SOURCE_DIR}/Pods/MOLFCMClient/Source/MOLFCMClient/MOLFCMClient.m"
"${CMAKE_SOURCE_DIR}/Pods/MOLFCMClient/Source/MOLFCMClient/MOLFCMClient.h"
)
target_link_libraries(MOLFCMClient PUBLIC MOLCertificate MOLAuthenticatingURLSession "-framework Cocoa")
target_compile_options(MOLFCMClient PRIVATE -fobjc-weak)
ImportCocoaPodsLibrary(
MOLXPCConnection
"${CMAKE_SOURCE_DIR}/Pods/MOLXPCConnection/Source/MOLXPCConnection"
"${CMAKE_SOURCE_DIR}/Pods/MOLXPCConnection/Source/MOLXPCConnection/MOLXPCConnection.h"
"${CMAKE_SOURCE_DIR}/Pods/MOLXPCConnection/Source/MOLXPCConnection/MOLXPCConnection.m"
)
target_link_libraries(MOLXPCConnection PUBLIC "-framework Cocoa")
ImportCocoaPodsLibrary(
OCMock
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMBlockCaller.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMockObject.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMArgAction.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMMacroState.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMInvocationStub.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCPartialMockObject.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMock.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMStubRecorder.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMRecorder.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMFunctions.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCProtocolMockObject.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCClassMockObject.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMArg.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMObserverRecorder.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMNotificationPoster.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMVerifier.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMConstraint.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMLocation.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCObserverMockObject.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMMacroState.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMArgAction.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMockObject.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMExpectationRecorder.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMBlockCaller.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMFunctions.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMStubRecorder.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMRecorder.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMInvocationExpectation.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCPartialMockObject.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMInvocationStub.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMPassByRefSetter.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMInvocationMatcher.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCClassMockObject.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMBlockArgCaller.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMReturnValueProvider.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCProtocolMockObject.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMFunctionsPrivate.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCObserverMockObject.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMLocation.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMConstraint.h"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMVerifier.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMNotificationPoster.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMObserverRecorder.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.m"
"${CMAKE_SOURCE_DIR}/Pods/OCMock/Source/OCMock/OCMArg.h"
)
target_link_libraries(OCMock PUBLIC "-framework Cocoa")
target_compile_options(OCMock PRIVATE -fno-objc-arc)
endfunction()
function(ImportCocoaPodsLibrary library_name source_directory)
set_source_files_properties(${ARGN} PROPERTIES GENERATED True)
add_library("${library_name}" STATIC ${ARGN})
add_dependencies("${library_name}" "${PROJECT_NAME}")
target_include_directories("${library_name}" PUBLIC
"${source_directory}"
"${CMAKE_SOURCE_DIR}/Pods/Headers/Public"
)
target_compile_options("${library_name}" PUBLIC
-Wno-deprecated -fmodules -fcxx-modules -fobjc-arc
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" OR CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options("${library_name}" PRIVATE -g3)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options("${library_name}" PRIVATE -O0)
else()
target_compile_options("${library_name}" PRIVATE -O3)
endif()
endfunction()
main()

View File

@@ -20,7 +20,5 @@
<true />
<key>ProcessType</key>
<string>Interactive</string>
<key>ThrottleInterval</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -1,12 +0,0 @@
<?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>
<!-- Minimal Configuration -->
<key>ClientMode</key>
<integer>1</integer>
<!-- For documentation of other keys, see the following URL:
https://github.com/google/santa/wiki/Configuration-Keys -->
</dict>
</plist>

View File

@@ -39,6 +39,12 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
mkdir -p /usr/local/bin
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
if [ ! -d /var/db/santa ] ; then
mkdir /var/db/santa
fi
cp ${SOURCE}/conf/com.google.santa.example.mobileconfig /var/db/santa
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/

View File

@@ -6,6 +6,15 @@
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
if [[ -d "binaries" ]]; then
SOURCE="."
elif [[ -d "../binaries" ]]; then
SOURCE=".."
else
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
exit 1
fi
/bin/launchctl remove com.google.santad
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
@@ -21,6 +30,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
/bin/rm -f /usr/local/bin/santactl # just a symlink
#uncomment to remove the config file and all databases, log files
#/bin/rm -rf /var/db/santa
#/bin/rm -f /var/log/santa*

View File

@@ -37,6 +37,7 @@ Two configuration methods can be used to control Santa: a local configuration pr
| MachineIDKey | String | The key to use on MachineIDPlist. |
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. Defaults to filelog |
| EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. |
| EnableMachineIDDecoration | Bool | If YES, this appends the MachineID to the end of each log line. Defaults to NO. |
*overridable by the sync server: run `santactl status` to check the current running config

25
Fuzzing/CMakeLists.txt Normal file
View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.10.0)
set(LIBFUZZER_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libFuzzer/bin/libFuzzer.a")
function(ImportLibFuzzer)
add_custom_command(
OUTPUT "${LIBFUZZER_PATH}"
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/libFuzzer/build.sh"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/libFuzzer/build.sh"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libFuzzer"
COMMENT "Building libFuzzer..."
)
add_custom_target(libfuzzer_builder DEPENDS "${LIBFUZZER_PATH}")
endfunction()
function(main)
ImportLibFuzzer()
add_subdirectory(santacache)
add_subdirectory(santad)
add_subdirectory(santactl)
endfunction()
main()

4
Fuzzing/libFuzzer/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
bin
llvm-*.src
llvm-*.src.tar.xz

109
Fuzzing/libFuzzer/build.sh Executable file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env bash
LLVM_VERSION='5.0.1'
LLVM_COMPILERRT_TARBALL_NAME="llvm-${LLVM_VERSION}.src.tar.xz"
LLVM_COMPILERRT_SRC_FOLDER_NAME=`echo "${LLVM_COMPILERRT_TARBALL_NAME}" | cut -d '.' -f 1-4`
LLVM_COMPILERRT_TARBALL_URL="http://releases.llvm.org/${LLVM_VERSION}/${LLVM_COMPILERRT_TARBALL_NAME}"
LIBFUZZER_FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LOG_FILE=`mktemp`
main() {
echo "libFuzzer build script"
echo " > Checking dependencies..."
checkDependencies || return 1
echo " > Entering libFuzzer folder..."
cd "${LIBFUZZER_FOLDER}" > /dev/null 2>&1
if [ $? -ne 0 ] ; then
echo "Failed to enter the libFuzzer folder: ${LIBFUZZER_FOLDER}"
return 1
fi
if [ ! -f "${LLVM_COMPILERRT_TARBALL_NAME}" ] ; then
echo " > Downloading the LLVM tarball..."
curl "${LLVM_COMPILERRT_TARBALL_URL}" -o "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to download the LLVM tarball"
return 1
fi
else
echo " > An existing LLVM tarball was found"
fi
if [ -d "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" ] ; then
echo " > Deleting existing LLVM folder..."
rm -rf "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to delete the existing source folder"
return 1
fi
fi
echo " > Extracting the LLVM tarball..."
tar xf "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
rm "${LLVM_COMPILERRT_TARBALL_NAME}" "${LLVM_COMPILERRT_SRC_FOLDER_NAME}"
dumpLogFile "Failed to extract the LLVM tarball"
return 1
fi
if [ -d "bin" ] ; then
echo " > Deleting existing bin folder..."
rm -rf "bin" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to delete the existing bin folder"
return 1
fi
fi
mkdir "bin" > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to create the bin folder"
return 1
fi
echo " > Building libFuzzer..."
( cd "bin" && "../${LLVM_COMPILERRT_SRC_FOLDER_NAME}/lib/Fuzzer/build.sh" ) > "${LOG_FILE}" 2>&1
if [ $? -ne 0 ] ; then
dumpLogFile "Failed to build the library"
return 1
fi
printf "\nFinished building libFuzzer\n"
rm "${LOG_FILE}"
return 0
}
checkDependencies() {
executable_list=( "clang++" "curl" "tar" )
for executable in "${executable_list[@]}" ; do
which "${executable}" > /dev/null 2>&1
if [ $? -ne 0 ] ; then
echo "The following program was not found: ${executable}"
return 1
fi
done
return 0
}
dumpLogFile() {
if [ $# -eq 1 ] ; then
local message="$1"
else
local message="An error has occurred"
fi
printf "${message}\n"
printf "Log file follows\n===\n"
cat "${LOG_FILE}"
printf "\n===\n"
rm "${LOG_FILE}"
}
main $@
exit $?

3
Fuzzing/santacache/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
santacache.dSYM
santacache

View File

@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.10.0)
project(santacache_fuzzer)
set(CMAKE_CXX_STANDARD 11)
function(main)
set(PROJECT_SOURCEFILES
src/main.cpp
)
add_executable("${PROJECT_NAME}" EXCLUDE_FROM_ALL ${PROJECT_SOURCEFILES})
add_dependencies("${PROJECT_NAME}" libfuzzer_builder)
target_link_libraries("${PROJECT_NAME}" PRIVATE "${LIBFUZZER_PATH}")
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/common"
"${CMAKE_SOURCE_DIR}/Source/santa-driver"
)
target_compile_options("${PROJECT_NAME}" PRIVATE
-g -O0 -Wno-deprecated-declarations
-fsanitize-coverage=trace-pc-guard
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
target_link_libraries("${PROJECT_NAME}" PRIVATE
-fsanitize=address -fno-omit-frame-pointer
-mno-omit-leaf-frame-pointer
)
AddFuzzTarget("${PROJECT_NAME}" 16 60 false)
endfunction()
main()

Binary file not shown.

View File

@@ -0,0 +1,41 @@
/// Copyright 2018 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <SantaCache.h>
#include <iostream>
#include <cstdint>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
std::uint64_t fields[2] = {};
if (size > 16) {
std::cout << "Invalid size! Start with -max_len=16\n";
return 1;
}
std::memcpy(fields, data, size);
decision_cache.set(fields[0], fields[1]);
auto returned_value = decision_cache.get(fields[0]);
if (returned_value != fields[1]) {
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value << "\n";
return 1;
}
return 0;
}

View File

@@ -0,0 +1,61 @@
cmake_minimum_required(VERSION 3.10.0)
project(santactl_fuzzer)
set(CMAKE_CXX_STANDARD 11)
function(main)
set(PROJECT_SOURCEFILES
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncConstants.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncStage.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncState.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTRule.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTLogging.m"
src/main.mm
)
add_executable("${PROJECT_NAME}" EXCLUDE_FROM_ALL ${PROJECT_SOURCEFILES})
target_compile_options("${PROJECT_NAME}" PRIVATE
-fobjc-arc -Wno-everything -fmodules -fcxx-modules
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
target_compile_definitions("${PROJECT_NAME}" PRIVATE
COCOAPODS=1
)
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync"
"${CMAKE_SOURCE_DIR}/Source/common"
"${CMAKE_SOURCE_DIR}/Source/santad"
)
target_link_libraries("${PROJECT_NAME}" PRIVATE
MOLCertificate MOLCodesignChecker
MOLAuthenticatingURLSession FMDB MOLFCMClient
)
#
# Add libfuzzer
#
add_dependencies("${PROJECT_NAME}" libfuzzer_builder)
target_link_libraries("${PROJECT_NAME}" PRIVATE "${LIBFUZZER_PATH}")
target_compile_options("${PROJECT_NAME}" PRIVATE
-g -O0 -Wno-deprecated-declarations
-fsanitize-coverage=trace-pc-guard
)
target_link_libraries("${PROJECT_NAME}" PRIVATE
-fsanitize=address -fno-omit-frame-pointer
-mno-omit-leaf-frame-pointer
)
AddFuzzTarget("${PROJECT_NAME}" 428 60 false)
endfunction()
main()

View File

@@ -0,0 +1,16 @@
{
"rules": [
{
"rule_type": "BINARY",
"policy": "BLACKLIST",
"sha256": "2dc104631939b4bdf5d6bccab76e166e37fe5e1605340cf68dab919df58b8eda",
"custom_msg": "blacklist firefox"
},
{
"rule_type": "CERTIFICATE",
"policy": "BLACKLIST",
"sha256": "e7726cf87cba9e25139465df5bd1557c8a8feed5c7dd338342d8da0959b63c8d",
"custom_msg": "blacklist dash app certificate"
}
]
}

View File

@@ -0,0 +1,62 @@
/// Copyright 2018 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#include <vector>
#include <SNTCommandSyncRuleDownload.h>
#include <SNTCommandSyncState.h>
#include <SNTCommandSyncConstants.h>
#include <SNTRule.h>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
if (!buffer) {
return 0;
}
NSError *error;
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error];
if (!response) {
return 0;
}
if (![response isKindOfClass:[NSDictionary class]]) {
return 0;
}
if (![response objectForKey:kRules]) {
return 0;
}
SNTCommandSyncState *state = [[SNTCommandSyncState alloc] init];
if (!state) {
return 0;
}
SNTCommandSyncRuleDownload *obj = [[SNTCommandSyncRuleDownload alloc] initWithState:state];
if (!obj) {
return 0;
}
for (NSDictionary *ruleDict in response[kRules]) {
SNTRule *rule = [obj ruleFromDictionary:ruleDict];
if (rule) {
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
}
}
return 0;
}

View File

@@ -0,0 +1,109 @@
cmake_minimum_required(VERSION 3.10.0)
project(santad_fuzzer)
set(CMAKE_CXX_STANDARD 11)
function(main)
GenerateFuzzTarget(src/databaseRuleAddRules.mm 45 60 true)
GenerateFuzzTarget(src/checkCacheForVnodeID.mm 16 60 true)
GenerateFuzzTarget(src/databaseRemoveEventsWithIDs.mm 80 60 true)
endfunction()
function(GenerateFuzzTarget source_file data_size timeout run_as_root)
# Generate one new project for each source file in the 'src' folder. Copy
# the project source files and settings from santactl
set(PROJECT_SANTACTLSOURCEFILES
"${CMAKE_SOURCE_DIR}/Source/santactl/SNTCommand.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/SNTCommand.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/SNTCommandController.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/SNTCommandController.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Resources/santactl-Info.plist"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/SNTCommandBundleInfo.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/SNTCommandCheckCache.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/SNTCommandFileInfo.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/SNTCommandFlushCache.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/SNTCommandRule.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/SNTCommandStatus.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/SNTCommandVersion.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/NSData+Zlib.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/NSData+Zlib.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSync.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncConstants.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncLogUpload.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncLogUpload.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncManager.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncManager.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncPostflight.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncPreflight.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncStage.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncStage.m"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncState.h"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync/SNTCommandSyncState.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTConfigurator.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTFileInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTRule.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTStoredEvent.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTSystemInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCControlInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCSyncdInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTLogging.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTDropRootPrivs.m"
)
get_filename_component(target_name "${source_file}" NAME_WE)
set(target_name "santad_${target_name}_fuzzer")
add_executable("${target_name}" EXCLUDE_FROM_ALL ${source_file} ${PROJECT_SANTACTLSOURCEFILES})
target_compile_options("${target_name}" PRIVATE
-fobjc-arc -Wno-everything -fmodules -fcxx-modules
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
target_compile_definitions("${target_name}" PRIVATE
COCOAPODS=1
)
target_include_directories("${target_name}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/santactl"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync"
"${CMAKE_SOURCE_DIR}/Source/santad"
"${CMAKE_SOURCE_DIR}/Source/common"
)
target_link_libraries("${target_name}" PRIVATE
MOLCertificate MOLCodesignChecker
MOLAuthenticatingURLSession FMDB MOLFCMClient
MOLXPCConnection
)
#
# Add libfuzzer
#
add_dependencies("${target_name}" libfuzzer_builder)
target_link_libraries("${target_name}" PRIVATE "${LIBFUZZER_PATH}")
target_compile_options("${target_name}" PRIVATE
-g -O0 -Wno-deprecated-declarations
-fsanitize-coverage=trace-pc-guard
)
target_link_libraries("${target_name}" PRIVATE
-fsanitize=address -fno-omit-frame-pointer
-mno-omit-leaf-frame-pointer
)
AddFuzzTarget("${target_name}" ${data_size} ${timeout} ${run_as_root})
endfunction()
main()

View File

@@ -0,0 +1 @@
К'.p▒└G╗М┐║ЙSЮ╝и▌РУерЭxt1iАЫШ9ы*H╩4R"═©$-├Уww╙+Р╝╘[┼иу╧oС┬ОwRpЗя≤х°е

View File

@@ -0,0 +1,55 @@
/// Copyright 2018 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTCommandController.h"
#import "SNTRule.h"
#import "SNTXPCControlInterface.h"
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
if (size > 16) {
std::cerr << "Invalid buffer size of " << size
<< " (should be <= 16)" << std::endl;
return 1;
}
santa_vnode_id_t vnodeID = {};
std::memcpy(&vnodeID, data, size);
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;;
} else if (action == ACTION_RESPOND_DENY) {
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;;
} else if (action == ACTION_UNSET) {
std::cerr << "File does not exist in cache" << std::endl;;
}
}];
return 0;
}

View File

@@ -0,0 +1,51 @@
/// Copyright 2018 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTCommandController.h"
#import "SNTRule.h"
#import "SNTXPCControlInterface.h"
#pragma pack(push, 1)
#pragma pack(pop)
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
auto *eventId = reinterpret_cast<const std::uint64_t *>(data);
std::size_t eventIdCount = size / sizeof(std::uint64_t);
if (eventIdCount == 0) {
return 0;
}
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:eventIdCount];
for (std::size_t i = 0; i < eventIdCount; i++) {
auto id = [NSNumber numberWithInteger:eventId[i]];
[eventIds addObject:id];
}
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
return 0;
}

View File

@@ -0,0 +1,73 @@
/// Copyright 2018 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <iostream>
#include <cstdint>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTCommandController.h"
#import "SNTRule.h"
#import "SNTXPCControlInterface.h"
#pragma pack(push, 1)
struct InputData {
std::uint32_t cleanSlate;
std::uint32_t state;
std::uint32_t type;
char hash[33];
};
#pragma pack(pop)
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
if (size > sizeof(InputData)) {
std::cerr << "Invalid buffer size of " << size
<< " (should be <= " << sizeof(InputData)
<< ")" << std::endl;
return 1;
}
InputData input_data = {};
std::memcpy(&input_data, data, size);
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = (SNTRuleState) input_data.state;
newRule.type = (SNTRuleType) input_data.type;
newRule.shasum = @(input_data.hash);
newRule.customMsg = @"";
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
cleanSlate:NO
reply:^(NSError *error) {
if (!error) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
}
}
}];
return 0;
}

View File

@@ -4,12 +4,12 @@ PODS:
- FMDB/standard (2.7.2)
- MOLAuthenticatingURLSession (2.4):
- MOLCertificate (~> 1.8)
- MOLCertificate (1.8)
- MOLCertificate (1.9)
- MOLCodesignChecker (1.10):
- MOLCertificate (~> 1.8)
- MOLFCMClient (1.7):
- MOLAuthenticatingURLSession (~> 2.4)
- MOLXPCConnection (1.1):
- MOLXPCConnection (1.2):
- MOLCodesignChecker (~> 1.9)
- OCMock (3.4.1)
@@ -22,15 +22,25 @@ DEPENDENCIES:
- MOLXPCConnection
- OCMock
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient
- MOLXPCConnection
- OCMock
SPEC CHECKSUMS:
FMDB: 6198a90e7b6900cfc046e6bc0ef6ebb7be9236aa
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
MOLCertificate: c999513316d511c69f290fbf313dfe8dca4ad592
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
MOLFCMClient: ee45348909351f232e2759c580329072ae7e02d4
MOLXPCConnection: de9d5535928f59766a768384e411077b83ec2f9c
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3
PODFILE CHECKSUM: ddca043a7ace9ec600c108621c56d13a50d17236
COCOAPODS: 1.4.0
COCOAPODS: 1.5.3

View File

@@ -43,7 +43,7 @@ correctly, but a rule for a binary's fingerprint will override a decision for a
certificate; i.e. you can whitelist a certificate while blacklisting a binary
signed with that certificate, or vice-versa.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature to that found in Managed Client (the precursor to configuration profiles, which used the same implementation mechanism), Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and not relying on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precendence.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature to that found in Managed Client (the precursor to configuration profiles, which used the same implementation mechanism), Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and not relying on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precedence.
* Failsafe cert rules: You cannot put in a deny rule that would block the certificate used to sign launchd, a.k.a. pid 1, and therefore all components used in macOS. The binaries in every OS update (and in some cases entire new versions) are therefore auto-whitelisted. This does not affect binaries from Apple's App Store, which use various certs that change regularly for common apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct separate cert than other Google apps.
@@ -124,7 +124,7 @@ A tool like Santa doesn't really lend itself to screenshots, so here's a video i
<img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif" alt="Santa Block Video" />
</p>
Building
Building with Xcode
========
```sh
@@ -144,6 +144,27 @@ and for security-reasons parts of Santa will not operate properly if not signed.
For more details on building see the [building.md](https://github.com/google/santa/blob/master/Docs/development/building.md) document.
Building with CMake
========
### General steps
1. Install Xcode and the command line tools
2. Install CMake using homebrew
3. Clone the santa source code repository
4. Set the signing key
5. Create a build folder and configure the project
6. Run make
Example
git clone https://github.com/google/santa.git
mkdir build
cd build
export CODESIGN_IDENTITY=XXX
cmake ../santa
make -j `sysctl -n hw.ncpu`
The CODESIGN_IDENTITY parameter can also be passed directly to CMake: cmake -DCODESIGN_IDENTITY=XXX /path/to/source/code
Kext Signing
============
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided

View File

@@ -155,20 +155,8 @@ namespace :tests do
desc "Tests: Kernel"
task :kernel do
Rake::Task['unload'].invoke()
Rake::Task['install:debug'].invoke()
Rake::Task['load_kext'].invoke
FileUtils.mkdir_p("/tmp/santa_kerneltests_tmp")
begin
puts "\033[?25l\033[12h" # hide cursor
puts "Running kernel tests"
system "cd /tmp/santa_kerneltests_tmp && sudo #{xcodebuilddir}/Debug/KernelTests"
rescue Exception
ensure
puts "\033[?25h\033[12l\n\n" # unhide cursor
FileUtils.rm_rf("/tmp/santa_kerneltests_tmp")
Rake::Task['unload_kext'].execute
end
Rake::Task['build:debug'].invoke()
system "sudo #{xcodebuilddir}/Debug/KernelTests"
end
end

View File

@@ -32,6 +32,7 @@
0D10BE861A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */; };
0D10BE871A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */; };
0D10BE891A0AAF6700C0C944 /* SNTDropRootPrivs.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */; };
0D10D18420C19445008251ED /* SNTCommandCacheHistogram.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10D18320C19445008251ED /* SNTCommandCacheHistogram.m */; };
0D1B477019A53419008CADD3 /* SNTAboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D1B476E19A53419008CADD3 /* SNTAboutWindowController.m */; };
0D1B477119A53419008CADD3 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0D1B476F19A53419008CADD3 /* AboutWindow.xib */; };
0D202D191CDD2EE500A88F16 /* SNTCommandSyncTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D202D181CDD2EE500A88F16 /* SNTCommandSyncTest.m */; };
@@ -184,6 +185,11 @@
C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; };
D1761D2DAE8C790F29AFE6C9 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D949AA996AEAC326A4F6596 /* libPods-LogicTests.a */; };
D221001320AB35FB003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */; };
D221001420AB3609003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */; };
D221001520AB360A003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */; };
D221001620AB360A003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */; };
D221001720AB360B003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */; };
D6D356C5BC8709C2E355C113 /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E3B4B2438711ACD7D560014 /* libPods-santactl.a */; };
/* End PBXBuildFile section */
@@ -286,6 +292,7 @@
0D0A1EC5191AB9B000B8450F /* SNTCommandSyncPostflight.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncPostflight.m; sourceTree = "<group>"; };
0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDropRootPrivs.m; sourceTree = "<group>"; };
0D10BE881A0AAC2100C0C944 /* SNTDropRootPrivs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTDropRootPrivs.h; sourceTree = "<group>"; };
0D10D18320C19445008251ED /* SNTCommandCacheHistogram.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTCommandCacheHistogram.m; sourceTree = "<group>"; };
0D1B476D19A53419008CADD3 /* SNTAboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTAboutWindowController.h; sourceTree = "<group>"; };
0D1B476E19A53419008CADD3 /* SNTAboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTAboutWindowController.m; sourceTree = "<group>"; };
0D1B476F19A53419008CADD3 /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = "<group>"; };
@@ -448,6 +455,8 @@
C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCSyncdInterface.m; sourceTree = "<group>"; };
C7FB56FE1DBFC213004E14EF /* SNTSyncdQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTSyncdQueue.h; sourceTree = "<group>"; };
C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTSyncdQueue.m; sourceTree = "<group>"; };
D221000F20AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTXPCUnprivilegedControlInterface.h; sourceTree = "<group>"; };
D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTXPCUnprivilegedControlInterface.m; sourceTree = "<group>"; };
D7DEE68F05C49966396A2F10 /* Pods-santad-santabs.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santad-santabs.release.xcconfig"; path = "Pods/Target Support Files/Pods-santad-santabs/Pods-santad-santabs.release.xcconfig"; sourceTree = "<group>"; };
D9B2D7095028D87E12E63EF7 /* Pods-santad-santabs.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santad-santabs.debug.xcconfig"; path = "Pods/Target Support Files/Pods-santad-santabs/Pods-santad-santabs.debug.xcconfig"; sourceTree = "<group>"; };
DF5FE88E12AB307864A53CE7 /* Pods-santactl.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santactl.release.xcconfig"; path = "Pods/Target Support Files/Pods-santactl/Pods-santactl.release.xcconfig"; sourceTree = "<group>"; };
@@ -743,6 +752,8 @@
0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */,
C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */,
C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */,
D221000F20AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.h */,
D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */,
);
path = common;
sourceTree = "<group>";
@@ -801,6 +812,7 @@
isa = PBXGroup;
children = (
C7DA62F81E241A02009BDF2C /* SNTCommandBundleInfo.m */,
0D10D18320C19445008251ED /* SNTCommandCacheHistogram.m */,
C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */,
0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */,
0DE4C8A518FF3B1700466D04 /* SNTCommandFlushCache.m */,
@@ -929,8 +941,6 @@
0D260DA818B68E12002A0B55 /* Sources */,
0D260DA918B68E12002A0B55 /* Frameworks */,
0D260DAA18B68E12002A0B55 /* Resources */,
D49A3AB950AFD99741E9AF89 /* [CP] Embed Pods Frameworks */,
23869BA352E2C86DEFE62819 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -949,7 +959,6 @@
0DD98E671A5DD02000A754C6 /* Update Version Info */,
0D35BD9A18FD71CE00921A21 /* Sources */,
0D35BD9B18FD71CE00921A21 /* Frameworks */,
2737FE8516A33567D3449943 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -969,8 +978,6 @@
0D385DB2180DE4A900418BC6 /* Sources */,
0D385DB3180DE4A900418BC6 /* Frameworks */,
0D385DB4180DE4A900418BC6 /* Resources */,
BA20035148DDEF5808B2C7EF /* [CP] Embed Pods Frameworks */,
DDE76075391100F3BCE3634E /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1012,7 +1019,6 @@
0DD98E661A5DCED300A754C6 /* Update Version Info */,
0D9A7F391759330400035EB5 /* Sources */,
0D9A7F3A1759330400035EB5 /* Frameworks */,
531BBB5BA4BF953D628D29AB /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1033,7 +1039,6 @@
C78227501E1C3C58006EB2D6 /* Sources */,
C78227511E1C3C58006EB2D6 /* Frameworks */,
C78227521E1C3C58006EB2D6 /* Resources */,
FCF4BDAA180BD63C32AC85DE /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1253,51 +1258,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
23869BA352E2C86DEFE62819 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
2737FE8516A33567D3449943 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santactl/Pods-santactl-resources.sh\"\n";
showEnvVarsInLog = 0;
};
531BBB5BA4BF953D628D29AB /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santad/Pods-santad-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9BF8830B1029605A497F13D5 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -1334,21 +1294,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
BA20035148DDEF5808B2C7EF /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Santa/Pods-Santa-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C7F95F971E23F9E7007A6BF5 /* Update bundle version */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -1384,51 +1329,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D49A3AB950AFD99741E9AF89 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
DDE76075391100F3BCE3634E /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Santa/Pods-Santa-resources.sh\"\n";
showEnvVarsInLog = 0;
};
FCF4BDAA180BD63C32AC85DE /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santad-santabs/Pods-santad-santabs-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -1484,6 +1384,7 @@
0DD0D492194F9BEF005F27EB /* SNTLogging.m in Sources */,
0DE71A761B95F7F900518526 /* SNTCachedDecision.m in Sources */,
0DCD605919115E5A006B445C /* SNTXPCNotifierInterface.m in Sources */,
D221001720AB360B003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */,
0DE50F691912B0CD007B2B0C /* SNTRule.m in Sources */,
0D202D1B1CDD465400A88F16 /* SNTCommandSyncState.m in Sources */,
C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */,
@@ -1505,12 +1406,14 @@
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */,
0DCD605C19117A90006B445C /* SNTCommandSyncPreflight.m in Sources */,
0D41640519197AD7006A356A /* SNTCommandSyncEventUpload.m in Sources */,
0D10D18420C19445008251ED /* SNTCommandCacheHistogram.m in Sources */,
0D42D2B919D2042900955F08 /* SNTConfigurator.m in Sources */,
0DF395641AB76A7900CBC520 /* NSData+Zlib.m in Sources */,
C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */,
0D10BE871A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */,
0DE4C8A618FF3B1700466D04 /* SNTCommandFlushCache.m in Sources */,
4092327A1A51B66400A04527 /* SNTCommandRule.m in Sources */,
D221001420AB3609003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */,
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */,
0D416401191974F1006A356A /* SNTCommandSyncState.m in Sources */,
0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */,
@@ -1537,6 +1440,7 @@
0D89310F1C931986002E8D74 /* SNTRule.m in Sources */,
0DCD605119115A06006B445C /* SNTXPCNotifierInterface.m in Sources */,
0DB77FD91CCE824A004DF060 /* SNTBlockMessage.m in Sources */,
D221001620AB360A003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */,
0D827E6519DF392E006EC811 /* SNTConfigurator.m in Sources */,
0D89310E1C931979002E8D74 /* SNTXPCControlInterface.m in Sources */,
0D385DF2180DE51600418BC6 /* SNTMessageWindowController.m in Sources */,
@@ -1575,6 +1479,7 @@
0DB8ACC1185662DC00FEF9C7 /* SNTApplication.m in Sources */,
0D9A7F421759330500035EB5 /* main.m in Sources */,
0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */,
D221001320AB35FB003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */,
C7C721B11E23FF300051FAA6 /* SNTXPCBundleServiceInterface.m in Sources */,
0DE71A751B95F7F900518526 /* SNTCachedDecision.m in Sources */,
C7FB56F61DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */,
@@ -1605,6 +1510,7 @@
C7C721B41E24042B0051FAA6 /* SNTStoredEvent.m in Sources */,
C7C721B51E2408BE0051FAA6 /* SNTFileInfo.m in Sources */,
C7BBA67E1E54C7E200F1E6A8 /* SNTXPCNotifierInterface.m in Sources */,
D221001520AB360A003C65C2 /* SNTXPCUnprivilegedControlInterface.m in Sources */,
C7479F071E5374BF0054C1CF /* SNTXPCControlInterface.m in Sources */,
C7479F091E5374E50054C1CF /* SNTRule.m in Sources */,
C79A23581E23F7E80037AFA8 /* main.m in Sources */,
@@ -1692,6 +1598,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INSTALL_PATH = "";
OTHER_CODE_SIGN_FLAGS = "";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-fcxx-modules",
@@ -1726,6 +1633,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INSTALL_PATH = "";
OTHER_CODE_SIGN_FLAGS = "";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-fcxx-modules",

View File

@@ -46,6 +46,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
debugAsWhichUser = "root"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"

View File

@@ -0,0 +1,139 @@
cmake_minimum_required(VERSION 3.10.0)
project(Santa)
function(CompileXib output_file_path_variable input_file_path)
get_filename_component(input_file_name "${input_file_path}" NAME_WE)
set(output_file_path "${CMAKE_CURRENT_BINARY_DIR}/${input_file_name}.nib")
add_custom_command(
OUTPUT "${output_file_path}"
COMMAND ibtool --compile "${output_file_path}" "${input_file_path}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Compiling: ${input_file_path}"
)
set("${output_file_path_variable}" "${output_file_path}" PARENT_SCOPE)
endfunction()
# It is important to only use absolute file paths with actool
function(CompileAssets asset_file_list_variable)
set(asset_file_list
"${CMAKE_CURRENT_BINARY_DIR}/AppIcon.icns"
"${CMAKE_CURRENT_BINARY_DIR}/Assets.car"
)
add_custom_command(
OUTPUT ${asset_file_list}
COMMAND xcrun actool --output-format=human-readable-text --notices --warnings --errors --platform macosx --minimum-deployment-target ${MACOSX_VERSION_MIN} --target-device mac --app-icon AppIcon --output-partial-info-plist "${CMAKE_CURRENT_BINARY_DIR}/Assets.plist" --compile "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Images.xcassets"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Packaging resources"
)
set("${asset_file_list_variable}" ${asset_file_list} PARENT_SCOPE)
endfunction()
function(main)
# Include all files to make IDEs happy
set(PROJECT_SOURCEFILES
SNTAboutWindowController.h
SNTAboutWindowController.m
SNTAccessibleTextField.h
SNTAccessibleTextField.m
SNTAppDelegate.h
SNTAppDelegate.m
SNTMessageWindow.h
SNTMessageWindow.m
SNTMessageWindowController.h
SNTMessageWindowController.m
SNTNotificationManager.h
SNTNotificationManager.m
main.m
# The "common" folder contains some of the files required to build this target
"${CMAKE_SOURCE_DIR}/Source/common/SNTConfigurator.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCControlInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCUnprivilegedControlInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTBlockMessage.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTRule.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTStoredEvent.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTSystemInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCBundleServiceInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCNotifierInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTLogging.m"
)
set(PROJECT_XIBFILES
"${CMAKE_CURRENT_SOURCE_DIR}/Resources/AboutWindow.xib"
"${CMAKE_CURRENT_SOURCE_DIR}/Resources/MessageWindow.xib"
)
foreach(xib_file ${PROJECT_XIBFILES})
CompileXib(output_nib_file_path "${xib_file}")
list(APPEND PROJECT_RESOURCES "${output_nib_file_path}")
list(APPEND PROJECT_SOURCEFILES "${output_nib_file_path}")
endforeach()
CompileAssets(asset_file_list)
foreach(asset_file ${asset_file_list})
list(APPEND PROJECT_RESOURCES "${asset_file}")
list(APPEND PROJECT_SOURCEFILES "${asset_file}")
endforeach()
add_executable("${PROJECT_NAME}" MACOSX_BUNDLE ${PROJECT_SOURCEFILES})
target_link_libraries("${PROJECT_NAME}" PRIVATE -ObjC)
set_target_properties("${PROJECT_NAME}" PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER "com.google.SantaGUI"
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Resources/SantaGUI-Info.plist"
RESOURCE "${PROJECT_RESOURCES}"
)
target_link_libraries("${PROJECT_NAME}" PRIVATE
MOLCertificate MOLCodesignChecker MOLXPCConnection
"-framework Cocoa"
)
target_compile_options("${PROJECT_NAME}" PRIVATE
-fobjc-arc -Wno-everything -fmodules
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
target_compile_options("${PROJECT_NAME}" PRIVATE -g3)
endif()
target_compile_definitions("${PROJECT_NAME}" PRIVATE
COCOAPODS=1
SANTAGUI=1
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions("${PROJECT_NAME}" PRIVATE DEBUG=1)
else()
target_compile_definitions("${PROJECT_NAME}" PRIVATE NDEBUG=1)
endif()
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/SantaGUI"
"${CMAKE_SOURCE_DIR}/Source/common"
"${CMAKE_SOURCE_DIR}/Source/santad"
)
add_custom_command(TARGET "${PROJECT_NAME}" POST_BUILD
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" "${CMAKE_BINARY_DIR}/Source/SantaGUI/Santa.app"
COMMENT "Signing ${PROJECT_NAME} with the following identity: ${CODESIGN_IDENTITY}"
)
# Ignore errors about the missing RESOURCE destination missing; this is a cmake bug
install(TARGETS "${PROJECT_NAME}" BUNDLE DESTINATION "/Applications")
endfunction()
main()

View File

@@ -9,20 +9,24 @@
<key>NSHumanReadableCopyright</key>
<string>Google, Inc.</string>
<key>CFBundleIdentifier</key>
<string>com.google.${PRODUCT_NAME:rfc1034identifier}GUI</string>
<string>com.google.SantaGUI</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<string>Santa</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<string>Santa</string>
<key>CFBundleVersion</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<string>${MACOSX_VERSION_MIN}</string>
<key>LSUIElement</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</plist>

View File

@@ -79,7 +79,7 @@
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.daemonListener = [[MOLXPCConnection alloc] initServerWithListener:listener];
self.daemonListener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.daemonListener.privilegedInterface = [SNTXPCNotifierInterface notifierInterface];
self.daemonListener.exportedObject = self.notificationManager;
self.daemonListener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
@@ -116,7 +116,7 @@
// Create listener for return connection from the bundle service.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.bundleListener = [[MOLXPCConnection alloc] initServerWithListener:listener];
self.bundleListener.exportedInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
self.bundleListener.privilegedInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
self.bundleListener.exportedObject = self.notificationManager;
self.bundleListener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);

View File

@@ -98,6 +98,13 @@
///
@property(readonly, nonatomic) NSString *eventLogPath;
///
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
/// has been overriden, this is the host's UUID.
/// Defaults to NO.
///
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
#pragma mark - GUI Settings
///

View File

@@ -72,6 +72,8 @@ static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
static NSString *const kEventLogType = @"EventLogType";
static NSString *const kEventLogPath = @"EventLogPath";
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
@@ -126,6 +128,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kMachineIDPlistKeyKey : string,
kEventLogType : string,
kEventLogPath : string,
kEnableMachineIDDecoration : number,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -280,6 +283,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -451,6 +458,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
}
- (BOOL)enableMachineIDDecoration {
NSNumber *number = self.configState[kEnableMachineIDDecoration];
return number ? [number boolValue] : NO;
}
#pragma mark Private
///

View File

@@ -21,9 +21,6 @@
#ifndef SANTA__COMMON__KERNELCOMMON_H
#define SANTA__COMMON__KERNELCOMMON_H
// Defines the lengths of paths and Vnode IDs passed around.
#define MAX_VNODE_ID_STR 21 // digits in UINT64_MAX + 1 for NULL-terminator
// Defines the name of the userclient class and the driver bundle ID.
#define USERCLIENT_CLASS "com_google_SantaDriver"
#define USERCLIENT_ID "com.google.santa-driver"
@@ -41,6 +38,7 @@ enum SantaDriverMethods {
kSantaUserClientClearCache,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
kSantaUserClientCacheBucketCount,
// Any methods supported by the driver should be added above this line to
// ensure this remains the count of methods.
@@ -82,10 +80,27 @@ typedef enum {
#define RESPONSE_VALID(x) \
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY)
// Struct to manage vnode IDs
typedef struct santa_vnode_id_t {
uint64_t fsid;
uint64_t fileid;
#ifdef __cplusplus
bool operator==(const santa_vnode_id_t& rhs) const {
return fsid == rhs.fsid && fileid == rhs.fileid;
}
// This _must not_ be used for anything security-sensitive. It exists solely to make
// the msleep/wakeup calls easier.
uint64_t unsafe_simple_id() const {
return (((uint64_t)fsid << 32) | fileid);
}
#endif
} santa_vnode_id_t;
// Message struct that is sent down the IODataQueue.
typedef struct {
santa_action_t action;
uint64_t vnode_id;
santa_vnode_id_t vnode_id;
uid_t uid;
gid_t gid;
pid_t pid;
@@ -99,4 +114,10 @@ typedef struct {
char pname[MAXPATHLEN];
} santa_message_t;
// Used for the kSantaUserClientCacheBucketCount request.
typedef struct {
uint16_t per_bucket[1024];
uint64_t start;
} santa_bucket_count_t;
#endif // SANTA__COMMON__KERNELCOMMON_H

View File

@@ -12,118 +12,56 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <MOLCertificate/MOLCertificate.h>
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTRule;
@class SNTStoredEvent;
@class MOLXPCConnection;
#import "SNTXPCUnprivilegedControlInterface.h"
///
/// Protocol implemented by santad and utilized by santactl
/// Protocol implemented by santad and utilized by santactl (privileged operations)
///
@protocol SNTDaemonControlXPC
@protocol SNTDaemonControlXPC <SNTUnprivilegedDaemonControlXPC>
///
/// Kernel ops
///
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
- (void)flushCache:(void (^)(BOOL))reply;
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply;
///
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(NSError *error))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTRule *))reply;
///
/// Decision ops
///
///
/// @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 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
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply;
///
/// Config ops
///
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)(void))reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)(void))reply;
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
- (void)syncCleanRequired:(void (^)(BOOL))reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)bundlesEnabled:(void (^)(BOOL))reply;
- (void)setBundlesEnabled:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
///
/// GUI Ops
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)pushNotifications:(void (^)(BOOL))reply;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
///
/// Bundle Ops
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
- (void)syncBundleEvent:(SNTStoredEvent *)event relatedEvents:(NSArray<SNTStoredEvent *> *)events;
@end
@interface SNTXPCControlInterface : NSObject
@interface SNTXPCControlInterface : SNTXPCUnprivilegedControlInterface
///
/// Returns the MachService ID for this service.
/// Internal method used to initialize the control interface
///
+ (NSString *)serviceId;
///
/// Returns an initialized NSXPCInterface for the SNTDaemonControlXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)controlInterface;
///
/// Retrieve a pre-configured MOLXPCConnection for communicating with santad.
/// Connections just needs any handlers set and then can be resumed and used.
///
+ (MOLXPCConnection *)configuredConnection;
+ (void)initializeControlInterface:(NSXPCInterface *)r;
@end

View File

@@ -25,8 +25,8 @@
return @"SantaXPCControl";
}
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
+ (void)initializeControlInterface:(NSXPCInterface *)r {
[super initializeControlInterface:r];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(databaseEventsPending:)
@@ -37,16 +37,11 @@
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
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];
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
[self initializeControlInterface:r];
return r;
}

View File

@@ -0,0 +1,119 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import <MOLCertificate/MOLCertificate.h>
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTRule;
@class SNTStoredEvent;
@class MOLXPCConnection;
///
/// Protocol implemented by santad and utilized by santactl (unprivileged operations)
///
@protocol SNTUnprivilegedDaemonControlXPC
///
/// Kernel ops
///
- (void)cacheCounts:(void (^)(uint64_t count))reply;
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
///
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
///
/// Decision ops
///
///
/// @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 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
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply;
///
/// Config ops
///
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)syncCleanRequired:(void (^)(BOOL))reply;
- (void)bundlesEnabled:(void (^)(BOOL))reply;
///
/// GUI Ops
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)pushNotifications:(void (^)(BOOL))reply;
///
/// Bundle Ops
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
- (void)syncBundleEvent:(SNTStoredEvent *)event relatedEvents:(NSArray<SNTStoredEvent *> *)events;
@end
@interface SNTXPCUnprivilegedControlInterface : NSObject
///
/// Returns the MachService ID for this service.
///
+ (NSString *)serviceId;
///
/// Returns an initialized NSXPCInterface for the SNTUnprivilegedDaemonControlXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)controlInterface;
///
/// Retrieve a pre-configured MOLXPCConnection for communicating with santad.
/// Connections just needs any handlers set and then can be resumed and used.
///
+ (MOLXPCConnection *)configuredConnection;
///
/// Internal method used to initialize the control interface
///
+ (void)initializeControlInterface:(NSXPCInterface *)r;
@end

View File

@@ -0,0 +1,54 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTXPCUnprivilegedControlInterface.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTRule.h"
#import "SNTStoredEvent.h"
@implementation SNTXPCUnprivilegedControlInterface
+ (NSString *)serviceId {
return @"SantaUnprivilegedXPCControl";
}
+ (void)initializeControlInterface:(NSXPCInterface *)r {
[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];
}
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
[self initializeControlInterface:r];
return r;
}
+ (MOLXPCConnection *)configuredConnection {
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceId]
privileged:YES];
c.remoteInterface = [self controlInterface];
return c;
}
@end

View File

@@ -0,0 +1,112 @@
cmake_minimum_required(VERSION 3.10.0)
project(santa-driver)
set(CMAKE_CXX_STANDARD 11)
function(main)
# Include all files to make IDEs happy
set(PROJECT_INFOPLIST "${CMAKE_CURRENT_SOURCE_DIR}/Resources/santa-driver-Info.plist")
set(PROJECT_SOURCEFILES
SantaCache.h
SantaDecisionManager.cc
SantaDecisionManager.h
SantaDriver.cc
SantaDriver.h
SantaDriverClient.cc
SantaDriverClient.h
main.c
"${PROJECT_INFOPLIST}"
)
add_library("${PROJECT_NAME}" MODULE ${PROJECT_SOURCEFILES})
set_target_properties("${PROJECT_NAME}" PROPERTIES
BUNDLE TRUE
BUNDLE_EXTENSION "kext"
MACOSX_BUNDLE_INFO_PLIST "${PROJECT_INFOPLIST}"
)
# As we have to include the executables in the same bundle, make sure
# we add them as dependencies
add_dependencies("${PROJECT_NAME}" santad santactl santabs)
target_compile_options("${PROJECT_NAME}" PRIVATE
-fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0
-nostdinc -fmodules -gmodules -fno-builtin -Wno-trigraphs -msoft-float -O0 -fno-common
-fapple-kext -fasm-blocks -fstrict-aliasing -MMD -MT dependencies
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
target_compile_options("${PROJECT_NAME}" PRIVATE -g3)
endif()
target_compile_options("${PROJECT_NAME}" PRIVATE
-Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module
-Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wdocumentation
-Wunreachable-code -Werror=deprecated-objc-isa-usage -Werror=objc-root-class -Wno-missing-braces
-Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter
-Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wconditional-uninitialized
-Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion
-Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion
-Wnon-literal-null-conversion -Wobjc-literal-conversion -Wshorten-64-to-32 -Wpointer-sign
-Wno-newline-eof -Wdeprecated-declarations -Wno-sign-conversion -Winfinite-recursion -Wcomma
-Wblock-capture-autoreleasing -Wstrict-prototypes -Wunguarded-availability
)
target_compile_definitions("${PROJECT_NAME}" PRIVATE
KERNEL
KERNEL_PRIVATE
DRIVER_PRIVATE
APPLE
NeXT
CMAKE
-DSANTA_VERSION="${SANTA_VERSION}"
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions("${PROJECT_NAME}" PRIVATE DEBUG)
else()
target_compile_definitions("${PROJECT_NAME}" PRIVATE NDEBUG)
endif()
target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE
"${XCODE_ROOT_FOLDER}/Platforms/MacOSX.platform/Developer/SDKs/MacOSX${MACOSX_SDK_VERSION}.sdk/System/Library/Frameworks/Kernel.framework/PrivateHeaders"
"${XCODE_ROOT_FOLDER}/Platforms/MacOSX.platform/Developer/SDKs/MacOSX${MACOSX_SDK_VERSION}.sdk/System/Library/Frameworks/Kernel.framework/Headers"
)
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/santa-driver"
"${CMAKE_SOURCE_DIR}/Source/common"
)
target_link_libraries("${PROJECT_NAME}" PRIVATE
-Xlinker -export_dynamic
-Xlinker -no_deduplicate
-Xlinker -kext
-nostdlib -lkmodc++ -lkmod -lcc_kext
)
add_custom_command(TARGET "${PROJECT_NAME}" POST_BUILD
# Copy santad and santactl
COMMAND "${CMAKE_COMMAND}" -E copy $<TARGET_FILE:santad> "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.kext/Contents/MacOS"
COMMAND "${CMAKE_COMMAND}" -E copy $<TARGET_FILE:santactl> "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.kext/Contents/MacOS"
# Copy the the santabs service
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_BINARY_DIR}/Source/santabs/santabs.xpc" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.kext/Contents/XPCServices/santabs.xpc"
# Sign everything
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" "${CMAKE_BINARY_DIR}/Source/santa-driver/santa-driver.kext"
COMMENT "Building and signing santa-driver.kext"
)
install(TARGETS "${PROJECT_NAME}" LIBRARY DESTINATION "/Library/Extensions")
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin/santactl)")
endfunction()
main()

View File

@@ -9,15 +9,15 @@
<key>NSHumanReadableCopyright</key>
<string>Google, Inc.</string>
<key>CFBundleIdentifier</key>
<string>com.google.${PRODUCT_NAME:rfc1034identifier}</string>
<string>com.google.santa-driver</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<string>santa-driver</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<string>santa-driver</string>
<key>CFBundleVersion</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
<key>IOKitPersonalities</key>
<dict>
<key>SantaDriver</key>

View File

@@ -40,9 +40,21 @@
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
#endif // KERNEL
/**
A type to specialize to help SantaCache with its hashing.
The default works for numeric types with a multiplicative hash
using a prime near to the golden ratio, per Knuth.
*/
template<typename T> uint64_t SantaCacheHasher(T const& t) {
return t * 11400714819323198549UL;
};
/**
A somewhat simple, concurrent linked-list hash table intended for use in IOKit kernel extensions.
Maps 64-bit unsigned integer keys to values.
The type used for keys must overload the == operator and a specialization of
SantaCacheHasher must exist for it.
Enforces a maximum size by clearing all entries if a new value
is added that would go over the maximum size declared at creation.
@@ -50,7 +62,7 @@
The number of buckets is calculated as `maximum_size` / `per_bucket`
rounded up to the next power of 2. Locking is done per-bucket.
*/
template<class T> class SantaCache {
template<typename KeyT, typename ValueT> class SantaCache {
public:
/**
Initialize a newly created cache.
@@ -65,8 +77,7 @@ template<class T> class SantaCache {
if (unlikely(per_bucket < 1)) per_bucket = 1;
if (unlikely(per_bucket > 64)) per_bucket = 64;
max_size_ = maximum_size;
bucket_count_ = 1 << (32 - __builtin_clz(
((uint32_t)max_size_ / per_bucket) - 1));
bucket_count_ = 1 << (32 - __builtin_clz(((uint32_t)max_size_ / per_bucket) - 1));
buckets_ = (struct bucket *)IOMalloc(bucket_count_ * sizeof(struct bucket));
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
}
@@ -82,13 +93,13 @@ template<class T> class SantaCache {
/**
Get an element from the cache. Returns zero_ if item doesn't exist.
*/
T get(uint64_t key) {
ValueT get(KeyT key) {
struct bucket *bucket = &buckets_[hash(key)];
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
if (entry->key == key) {
T val = entry->value;
ValueT val = entry->value;
unlock(bucket);
return val;
}
@@ -102,103 +113,39 @@ template<class T> class SantaCache {
Set an element in the cache.
@note If the cache is full when this is called, this will
empty the cache before inserting the new value.
empty the cache before inserting the new value.
@param key, The key
@param value, The value with parameterized type
@param previous_value, If the has_prev_value parameter is true the new
value will only be set if this parameter is equal to the provided value.
This allows set to become a CAS operation.
@param has_prev_value, Pass true if previous_value should be used.
@param key The key.
@param value The value with parameterized type.
@return the previous value (which may be zero_)
@return true if the value was set.
*/
T set(uint64_t key, T value, T previous_value, bool has_prev_value) {
struct bucket *bucket = &buckets_[hash(key)];
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
struct entry *previous_entry = nullptr;
while (entry != nullptr) {
if (entry->key == key) {
T existing_value = entry->value;
if (has_prev_value && previous_value != existing_value) {
unlock(bucket);
return existing_value;
}
entry->value = value;
if (value == zero_) {
if (previous_entry != nullptr) {
previous_entry->next = entry->next;
} else {
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
}
IOFreeAligned(entry, sizeof(struct entry));
OSDecrementAtomic(&count_);
}
unlock(bucket);
return existing_value;
}
previous_entry = entry;
entry = entry->next;
}
// If value is zero_, we're clearing but there's nothing to clear
// so we don't need to do anything else. Alternatively, if has_prev_value
// is true and is not zero_ we don't want to set a value.
if (value == zero_ || (has_prev_value && previous_value != zero_)) {
unlock(bucket);
return zero_;
}
// Check that adding this new item won't take the cache
// over its maximum size.
if (count_ + 1 > max_size_) {
unlock(bucket);
lock(&clear_bucket_);
// Check again in case clear has already run while waiting for lock
if (count_ + 1 > max_size_) {
clear();
}
lock(bucket);
unlock(&clear_bucket_);
}
// Allocate a new entry, set the key and value, then put this new entry at
// the head of this bucket's linked list.
struct entry *new_entry = (struct entry *)IOMallocAligned(
sizeof(struct entry), 2);
new_entry->key = key;
new_entry->value = value;
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
OSIncrementAtomic(&count_);
unlock(bucket);
return zero_;
}
/**
Overload to allow setting without providing a previous value
*/
T set(uint64_t key, T value) {
bool set(KeyT key, ValueT value) {
return set(key, value, {}, false);
}
/**
Overload to allow setting while providing a previous value
Set an element in the cache.
@note If the cache is full when this is called, this will
empty the cache before inserting the new value.
@param key The key.
@param value The value with parameterized type.
@param previous_value the new value will only be set if this
parameter is equal to the existing value in the cache.
This allows set to become a CAS operation.
@return true if the value was set
*/
T set(uint64_t key, T value, T previous_value) {
bool set(KeyT key, ValueT value, ValueT previous_value) {
return set(key, value, previous_value, true);
}
/**
An alias for `set(key, zero_)`
*/
inline void remove(uint64_t key) {
inline void remove(KeyT key) {
set(key, zero_);
}
@@ -238,10 +185,42 @@ template<class T> class SantaCache {
return count_;
}
/**
Fill in the per_bucket_counts array with the number of entries in each bucket.
The per_buckets_count array will contain the per-bucket counts, up to the number
in array_size. The start_bucket parameter will determine which bucket to start off
with and upon return will contain either 0 if no buckets are remaining or the next
bucket to begin with when called again.
*/
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
if (per_bucket_counts == nullptr || array_size == nullptr || start_bucket == nullptr) return;
uint64_t start = *start_bucket;
uint16_t size = *array_size;
if (start + size > bucket_count_) size = bucket_count_ - start;
for (uint16_t i = 0; i < size; ++i) {
uint16_t count = 0;
struct bucket *bucket = &buckets_[start++];
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
if (entry->value != zero_) ++count;
entry = entry->next;
}
unlock(bucket);
per_bucket_counts[i] = count;
}
*array_size = size;
*start_bucket = (start >= bucket_count_) ? 0 : start;
}
private:
struct entry {
uint64_t key;
T value;
KeyT key;
ValueT value;
struct entry *next;
};
@@ -251,6 +230,88 @@ template<class T> class SantaCache {
struct entry *head;
};
/**
Set an element in the cache.
@note If the cache is full when this is called, this will
empty the cache before inserting the new value.
@param key The key
@param value The value with parameterized type
@param previous_value If has_prev_value is true, the new value will only
be set if this parameter is equal to the existing value in the cache.
This allows set to become a CAS operation.
@param has_prev_value Pass true if previous_value should be used.
@return true if the entry was set, false if it was not
*/
bool set(KeyT key, ValueT value, ValueT previous_value, bool has_prev_value) {
struct bucket *bucket = &buckets_[hash(key)];
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
struct entry *previous_entry = nullptr;
while (entry != nullptr) {
if (entry->key == key) {
ValueT existing_value = entry->value;
if (has_prev_value && previous_value != existing_value) {
unlock(bucket);
return false;
}
entry->value = value;
if (value == zero_) {
if (previous_entry != nullptr) {
previous_entry->next = entry->next;
} else {
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
}
IOFreeAligned(entry, sizeof(struct entry));
OSDecrementAtomic(&count_);
}
unlock(bucket);
return true;
}
previous_entry = entry;
entry = entry->next;
}
// If value is zero_, we're clearing but there's nothing to clear
// so we don't need to do anything else. Alternatively, if has_prev_value
// is true and is not zero_ we don't want to set a value.
if (value == zero_ || (has_prev_value && previous_value != zero_)) {
unlock(bucket);
return false;
}
// Check that adding this new item won't take the cache
// over its maximum size.
if (count_ + 1 > max_size_) {
unlock(bucket);
lock(&clear_bucket_);
// Check again in case clear has already run while waiting for lock
if (count_ + 1 > max_size_) {
clear();
}
lock(bucket);
unlock(&clear_bucket_);
}
// Allocate a new entry, set the key and value, then put this new entry at
// the head of this bucket's linked list.
struct entry *new_entry = (struct entry *)IOMallocAligned(sizeof(struct entry), 2);
new_entry->key = key;
new_entry->value = value;
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
OSIncrementAtomic(&count_);
unlock(bucket);
return true;
}
/**
Lock a bucket. Spins until the lock is acquired.
*/
@@ -277,7 +338,7 @@ template<class T> class SantaCache {
/**
Holder for a 'zero' entry for the current type
*/
const T zero_ = {};
const ValueT zero_ = {};
/**
Special bucket used when automatically clearing due to size
@@ -288,13 +349,9 @@ template<class T> class SantaCache {
/**
Hash a key to determine which bucket it belongs in.
Multiplicative hash using a prime near to the golden ratio, per Knuth.
This seems to have good bucket distribution generally and for the range of
values we expect to see.
*/
inline uint64_t hash(uint64_t input) const {
return (input * 11400714819323198549ul) % bucket_count_;
inline uint64_t hash(KeyT input) const {
return SantaCacheHasher<KeyT>(input) % bucket_count_;
}
};

View File

@@ -19,6 +19,10 @@ OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
#pragma mark Object Lifecycle
template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& s) {
return (SantaCacheHasher<uint64_t>(s.fsid) << 1) ^ SantaCacheHasher<uint64_t>(s.fileid);
}
bool SantaDecisionManager::init() {
if (!super::init()) return false;
@@ -29,9 +33,8 @@ bool SantaDecisionManager::init() {
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
root_decision_cache_ = new SantaCache<uint64_t>(5000, 2);
non_root_decision_cache_ = new SantaCache<uint64_t>(500, 2);
vnode_pid_map_ = new SantaCache<uint64_t>(2000, 5);
decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(10000, 2);
vnode_pid_map_ = new SantaCache<santa_vnode_id_t, uint64_t>(2000, 5);
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
@@ -42,17 +45,12 @@ bool SantaDecisionManager::init() {
if (!log_dataqueue_) return kIOReturnNoMemory;
client_pid_ = 0;
root_fsid_ = 0;
ts_ = { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
return true;
}
void SantaDecisionManager::free() {
delete root_decision_cache_;
delete non_root_decision_cache_;
delete decision_cache_;
delete vnode_pid_map_;
if (decision_dataqueue_lock_) {
@@ -93,17 +91,6 @@ void SantaDecisionManager::ConnectClient(pid_t pid) {
client_pid_ = pid;
// Determine root fsid
vfs_context_t ctx = vfs_context_create(NULL);
if (ctx) {
vnode_t root = vfs_rootvnode();
if (root) {
root_fsid_ = GetVnodeIDForVnode(ctx, root) >> 32;
vnode_put(root);
}
vfs_context_rele(ctx);
}
// Any decisions made while the daemon wasn't
// connected should be cleared
ClearCache();
@@ -112,8 +99,8 @@ void SantaDecisionManager::ConnectClient(pid_t pid) {
failed_log_queue_requests_ = 0;
}
void SantaDecisionManager::DisconnectClient(bool itDied) {
if (client_pid_ < 1) return;
void SantaDecisionManager::DisconnectClient(bool itDied, pid_t pid) {
if (client_pid_ == 0 || (pid > 0 && pid != client_pid_)) return;
client_pid_ = 0;
// Ask santad to shutdown, in case it's running.
@@ -210,74 +197,58 @@ kern_return_t SantaDecisionManager::StopListener() {
#pragma mark Cache Management
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<uint64_t>* SantaDecisionManager::CacheForIdentifier(
const uint64_t identifier) {
return (identifier >> 32 == root_fsid_) ?
root_decision_cache_ : non_root_decision_cache_;
}
void SantaDecisionManager::AddToCache(
uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
auto decision_cache = CacheForIdentifier(identifier);
santa_vnode_id_t identifier, santa_action_t decision, uint64_t microsecs) {
switch (decision) {
case ACTION_REQUEST_BINARY:
decision_cache->set(identifier, val, 0);
decision_cache_->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
break;
case ACTION_RESPOND_ACK:
decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56));
decision_cache_->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_DENY:
// TODO(bur): Avoid calling set() twice, finding and locking buckets is fast, but not free.
if (decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
case ACTION_RESPOND_DENY: {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
if (!decision_cache_->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache_->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
break;
}
default:
break;
}
wakeup((void *)identifier);
wakeup((void *)identifier.unsafe_simple_id());
}
void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
CacheForIdentifier(identifier)->remove(identifier);
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
void SantaDecisionManager::RemoveFromCache(santa_vnode_id_t identifier) {
if (unlikely(identifier.fsid == 0 && identifier.fileid == 0)) return;
decision_cache_->remove(identifier);
wakeup((void *)identifier.unsafe_simple_id());
}
uint64_t SantaDecisionManager::RootCacheCount() const {
return root_decision_cache_->count();
uint64_t SantaDecisionManager::CacheCount() const {
return decision_cache_->count();
}
uint64_t SantaDecisionManager::NonRootCacheCount() const {
return non_root_decision_cache_->count();
void SantaDecisionManager::ClearCache() {
decision_cache_->clear();
}
void SantaDecisionManager::ClearCache(bool non_root_only) {
if (!non_root_only) root_decision_cache_->clear();
non_root_decision_cache_->clear();
void SantaDecisionManager::CacheBucketCount(
uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
decision_cache_->bucket_counts(per_bucket_counts, array_size, start_bucket);
}
#pragma mark Decision Fetching
santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
santa_action_t SantaDecisionManager::GetFromCache(santa_vnode_id_t identifier) {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
auto decision_cache = CacheForIdentifier(identifier);
uint64_t cache_val = decision_cache->get(identifier);
uint64_t cache_val = decision_cache_->get(identifier);
if (cache_val == 0) return result;
// Decision is stored in upper 8 bits, timestamp in remaining 56.
@@ -288,7 +259,7 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache->remove(identifier);
decision_cache_->remove(identifier);
return ACTION_UNSET;
}
}
@@ -298,7 +269,7 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
}
santa_action_t SantaDecisionManager::GetFromDaemon(
santa_message_t *message, uint64_t identifier) {
santa_message_t *message, santa_vnode_id_t identifier) {
auto return_action = ACTION_UNSET;
#ifdef DEBUG
@@ -326,7 +297,7 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
// request, indicated with ACTION_RESPOND_ACK.
auto cache_check_count = 0;
do {
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
msleep((void *)message->vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} while (ClientConnected() &&
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
@@ -353,7 +324,7 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id) {
const santa_vnode_id_t vnode_id) {
while (true) {
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
@@ -368,7 +339,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
// until AddToCache is called, indicating a response has arrived.
msleep((void *)vnode_id, NULL, 0, "", &ts_);
msleep((void *)vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
} else {
break;
}
@@ -403,7 +374,6 @@ bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
LOGE("Failed to queue more than %d decision requests, killing daemon",
kMaxDecisionQueueFailures);
proc_signal(client_pid_, SIGKILL);
client_pid_ = 0;
}
}
lck_mtx_unlock(decision_dataqueue_lock_);
@@ -417,10 +387,6 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
if (failed_log_queue_requests_++ == 0) {
LOGW("Dropping log queue messages");
}
// If enqueue failed, pop an item off the queue and try again.
uint32_t dataSize = 0;
log_dataqueue_->dequeue(0, &dataSize);
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
} else {
if (failed_log_queue_requests_ > 0) {
failed_log_queue_requests_--;
@@ -448,24 +414,11 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
int *errno) {
// Get ID for the vnode
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
if (!vnode_id) return KAUTH_RESULT_DEFER;
if (vnode_id.fsid == 0 && vnode_id.fileid == 0) return KAUTH_RESULT_DEFER;
// Fetch decision
auto returnedAction = FetchDecision(cred, vp, vnode_id);
// If file has dirty blocks, remove from cache and deny. This would usually
// be the case if a file has been written to and flushed but not yet
// closed.
if (vnode_hasdirtyblks(vp)) {
RemoveFromCache(vnode_id);
returnedAction = ACTION_RESPOND_DENY;
char path[MAXPATHLEN];
int len = MAXPATHLEN;
path[MAXPATHLEN - 1] = 0;
LOGW("file has dirty blocks: %s", vn_getpath(vp, path, &len) ? "unknown" : path);
}
switch (returnedAction) {
case ACTION_RESPOND_ALLOW: {
auto proc = vfs_context_proc(ctx);

View File

@@ -64,7 +64,7 @@ class SantaDecisionManager : public OSObject {
void ConnectClient(pid_t pid);
/// Called by SantaDriverClient when a client disconnects
void DisconnectClient(bool itDied = false);
void DisconnectClient(bool itDied = false, pid_t pid = proc_selfpid());
/// Returns whether a client is currently connected or not.
bool ClientConnected() const;
@@ -86,7 +86,7 @@ class SantaDecisionManager : public OSObject {
kern_return_t StopListener();
/// Adds a decision to the cache, with a timestamp.
void AddToCache(uint64_t identifier,
void AddToCache(santa_vnode_id_t identifier,
const santa_action_t decision,
const uint64_t microsecs = GetCurrentUptime());
@@ -94,20 +94,30 @@ class SantaDecisionManager : public OSObject {
Fetches a response from the cache, first checking to see if the entry
has expired.
*/
santa_action_t GetFromCache(uint64_t identifier);
santa_action_t GetFromCache(santa_vnode_id_t identifier);
/// Checks to see if a given identifier is in the cache and removes it.
void RemoveFromCache(uint64_t identifier);
void RemoveFromCache(santa_vnode_id_t identifier);
/// Returns the number of entries in the cache.
uint64_t RootCacheCount() const;
uint64_t NonRootCacheCount() const;
uint64_t CacheCount() const;
/// Clears the cache.
void ClearCache();
/**
Clears the cache(s). If non_root_only is true, only the non-root cache
is cleared.
Fills out the per_bucket_counts array with the number of items in each bucket in the
non-root decision cache.
@param per_bucket_counts An array of uint16_t's to fill in with the number of items in each
bucket. The size of this array is expected to equal array_size.
@param array_size The size of the per_bucket_counts array on input. Upon return this will be
updated to the number of slots that were actually used.
@param start_bucket If non-zero this is the bucket in the cache to start from. Upon return this
will be the next numbered bucket to start from for subsequent requests.
*/
void ClearCache(bool non_root_only = false);
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket);
/// Increments the count of active callbacks pending.
void IncrementListenerInvocations();
@@ -120,15 +130,18 @@ class SantaDecisionManager : public OSObject {
@param ctx The VFS context to use.
@param vp The Vnode to get the ID for
@return uint64_t The Vnode ID as a 64-bit unsigned int.
@return santa_vnode_id_t The Vnode ID.
*/
static inline uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) {
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, ctx);
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
return {
.fsid = vap.va_fsid,
.fileid = vap.va_fileid
};
}
/**
@@ -198,7 +211,7 @@ class SantaDecisionManager : public OSObject {
@param identifier The vnode ID string for this request
@return santa_action_t The response for this request
*/
santa_action_t GetFromDaemon(santa_message_t *message, uint64_t identifier);
santa_action_t GetFromDaemon(santa_message_t *message, santa_vnode_id_t identifier);
/**
Fetches an execution decision for a file, first using the cache and then
@@ -212,7 +225,7 @@ class SantaDecisionManager : public OSObject {
@return santa_action_t The response for this request
*/
santa_action_t FetchDecision(
const kauth_cred_t cred, const vnode_t vp, const uint64_t vnode_id);
const kauth_cred_t cred, const vnode_t vp, const santa_vnode_id_t vnode_id);
/**
Posts the requested message to the decision data queue.
@@ -266,21 +279,8 @@ class SantaDecisionManager : public OSObject {
return (uint64_t)((sec * 1000000) + usec);
}
SantaCache<uint64_t> *root_decision_cache_;
SantaCache<uint64_t> *non_root_decision_cache_;
SantaCache<uint64_t> *vnode_pid_map_;
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<uint64_t>* CacheForIdentifier(const uint64_t identifier);
// This is the file system ID of the root filesystem,
// used to determine which cache to use for requests
uint32_t root_fsid_;
SantaCache<santa_vnode_id_t, uint64_t> *decision_cache_;
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;
lck_grp_t *sdm_lock_grp_;
lck_grp_attr_t *sdm_lock_grp_attr_;
@@ -301,19 +301,20 @@ class SantaDecisionManager : public OSObject {
kauth_listener_t vnode_listener_;
kauth_listener_t fileop_listener_;
struct timespec ts_;
struct timespec ts_= { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
};
/**
The kauth callback function for the Vnode scope
@param actor's credentials
@param data that was passed when the listener was registered
@param action that was requested
@param VFS context
@param Vnode being operated on
@param Parent Vnode. May be nullptr.
@param Pointer to an errno-style error.
@param credential actor's credentials
@param idata data that was passed when the listener was registered
@param action action that was requested
@param arg0 VFS context
@param arg1 Vnode being operated on
@param arg2 Parent Vnode. May be nullptr.
@param arg3 Pointer to an errno-style error.
*/
extern "C" int vnode_scope_callback(
kauth_cred_t credential,
@@ -327,13 +328,13 @@ extern "C" int vnode_scope_callback(
/**
The kauth callback function for the FileOp scope
@param actor's credentials
@param data that was passed when the listener was registered
@param action that was requested
@param depends on action, usually the vnode ref.
@param depends on action.
@param depends on action, usually 0.
@param depends on action, usually 0.
@param credential actor's credentials
@param idata data that was passed when the listener was registered
@param action action that was requested
@param arg0 depends on action, usually the vnode ref.
@param arg1 depends on action.
@param arg2 depends on action, usually 0.
@param arg3 depends on action, usually 0.
*/
extern "C" int fileop_scope_callback(
kauth_cred_t credential,

View File

@@ -37,34 +37,38 @@ bool SantaDriverClient::initWithTask(
bool SantaDriverClient::start(IOService *provider) {
myProvider = OSDynamicCast(com_google_SantaDriver, provider);
if (!myProvider) return false;
if (!super::start(provider)) return false;
decisionManager = myProvider->GetDecisionManager();
if (!decisionManager) return false;
decisionManager->retain();
return true;
return super::start(provider);
}
void SantaDriverClient::stop(IOService *provider) {
super::stop(provider);
myProvider = nullptr;
decisionManager->release();
decisionManager = nullptr;
super::stop(provider);
}
IOReturn SantaDriverClient::clientDied() {
LOGI("Client died.");
decisionManager->DisconnectClient(true);
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
}
IOReturn SantaDriverClient::clientClose() {
decisionManager->DisconnectClient(true);
return terminate(kIOServiceSynchronous) ? kIOReturnSuccess : kIOReturnError;
LOGI("Client disconnected.");
decisionManager->DisconnectClient();
return terminate(0) ? kIOReturnSuccess : kIOReturnError;
}
bool SantaDriverClient::terminate(IOOptionBits options) {
decisionManager->DisconnectClient();
LOGI("Client disconnected.");
bool SantaDriverClient::didTerminate(IOService *provider, IOOptionBits options, bool *defer) {
decisionManager->DisconnectClient(false, 0);
if (myProvider && myProvider->isOpen(this)) myProvider->close(this);
return super::terminate(options);
return super::didTerminate(provider, options, defer);
}
#pragma mark Fetching memory and data queue notifications
@@ -133,9 +137,10 @@ IOReturn SantaDriverClient::allow_binary(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
if (!vnode_id) return kIOReturnInvalid;
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ALLOW);
return kIOReturnSuccess;
}
@@ -145,9 +150,10 @@ IOReturn SantaDriverClient::deny_binary(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
if (!vnode_id) return kIOReturnInvalid;
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_DENY);
return kIOReturnSuccess;
}
@@ -157,9 +163,10 @@ IOReturn SantaDriverClient::acknowledge_binary(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
if (!vnode_id) return kIOReturnInvalid;
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ACK, 0);
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
me->decisionManager->AddToCache(*vnode_id, ACTION_RESPOND_ACK);
return kIOReturnSuccess;
}
@@ -169,8 +176,7 @@ IOReturn SantaDriverClient::clear_cache(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const bool non_root_only = static_cast<const bool>(arguments->scalarInput[0]);
me->decisionManager->ClearCache(non_root_only);
me->decisionManager->ClearCache();
return kIOReturnSuccess;
}
@@ -180,8 +186,7 @@ IOReturn SantaDriverClient::cache_count(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
arguments->scalarOutput[0] = me->decisionManager->RootCacheCount();
arguments->scalarOutput[1] = me->decisionManager->NonRootCacheCount();
arguments->scalarOutput[0] = me->decisionManager->CacheCount();
return kIOReturnSuccess;
}
@@ -190,8 +195,27 @@ IOReturn SantaDriverClient::check_cache(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t input = static_cast<const uint64_t>(arguments->scalarInput[0]);
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(input);
if (arguments->structureInputSize != sizeof(santa_vnode_id_t)) return kIOReturnInvalid;
santa_vnode_id_t *vnode_id = (santa_vnode_id_t *)arguments->structureInput;
if (vnode_id->fsid == 0 || vnode_id->fileid == 0) return kIOReturnInvalid;
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(*vnode_id);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::cache_bucket_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
santa_bucket_count_t *counts = reinterpret_cast<santa_bucket_count_t *>(
arguments->structureOutput);
const santa_bucket_count_t *input = reinterpret_cast<const santa_bucket_count_t *>(
arguments->structureInput);
uint16_t s = sizeof(counts->per_bucket) / sizeof(uint16_t);
counts->start = input->start;
me->decisionManager->CacheBucketCount(counts->per_bucket, &s, &(counts->start));
return kIOReturnSuccess;
}
@@ -209,12 +233,14 @@ IOReturn SantaDriverClient::externalMethod(
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::deny_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::acknowledge_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
{ &SantaDriverClient::check_cache, 1, 0, 1, 0 }
{ &SantaDriverClient::allow_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::deny_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::acknowledge_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::clear_cache, 0, 0, 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 1, 0 },
{ &SantaDriverClient::check_cache, 0, sizeof(santa_vnode_id_t), 1, 0 },
{ &SantaDriverClient::cache_bucket_count, 0, sizeof(santa_bucket_count_t),
0, sizeof(santa_bucket_count_t) },
};
if (selector > static_cast<UInt32>(kSantaUserClientNMethods)) {

View File

@@ -47,11 +47,14 @@ class com_google_SantaDriverClient : public IOUserClient {
/// Called when this class is stopping
void stop(IOService *provider) override;
/// Called when a client disconnects
/// Called when a client manually disconnects (via IOServiceClose)
IOReturn clientClose() override;
/// Called when the driver is shutting down
bool terminate(IOOptionBits options) override;
/// Called when a client dies
IOReturn clientDied() override;
/// Called during termination
bool didTerminate(IOService* provider, IOOptionBits options, bool* defer) override;
/// Called in clients with IOConnectSetNotificationPort
IOReturn registerNotificationPort(
@@ -105,6 +108,11 @@ class com_google_SantaDriverClient : public IOUserClient {
static IOReturn check_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in each cache bucket.
/// Input and output are both an instance of santa_bucket_count_t.
static IOReturn cache_bucket_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
private:
com_google_SantaDriver *myProvider;
SantaDecisionManager *decisionManager;

View File

@@ -0,0 +1,11 @@
#include <mach/mach_types.h>
extern kern_return_t _start(kmod_info_t *ki, void *data);
extern kern_return_t _stop(kmod_info_t *ki, void *data);
__attribute__((visibility("default"))) KMOD_EXPLICIT_DECL(com.google.santa-driver, SANTA_VERSION, _start, _stop)
__private_extern__ kmod_start_func_t *_realmain = 0;
__private_extern__ kmod_stop_func_t *_antimain = 0;
__private_extern__ int _kext_apple_cc = __APPLE_CC__ ;

View File

@@ -0,0 +1,68 @@
cmake_minimum_required(VERSION 3.10.0)
project(santabs)
function(main)
# Include all files to make IDEs happy
set(PROJECT_SOURCEFILES
SNTBundleService.h
SNTBundleService.m
main.m
Resources/santabs-Info.plist
# The "common" folder contains some of the files required to build this target
"${CMAKE_SOURCE_DIR}/Source/common/SNTFileInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTStoredEvent.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCBundleServiceInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCNotifierInterface.m"
)
add_executable("${PROJECT_NAME}" MACOSX_BUNDLE ${PROJECT_SOURCEFILES})
target_link_libraries("${PROJECT_NAME}" PRIVATE -ObjC)
set_target_properties("${PROJECT_NAME}" PROPERTIES
BUNDLE_EXTENSION "xpc"
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Resources/santabs-Info.plist"
)
target_link_libraries("${PROJECT_NAME}" PRIVATE
MOLCertificate MOLCodesignChecker
FMDB MOLXPCConnection
"-framework Cocoa"
)
target_compile_options("${PROJECT_NAME}" PRIVATE
-fobjc-arc -Wno-everything -fmodules
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
target_compile_options("${PROJECT_NAME}" PRIVATE -g3)
endif()
target_compile_definitions("${PROJECT_NAME}" PRIVATE
COCOAPODS=1
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions("${PROJECT_NAME}" PRIVATE DEBUG=1)
else()
target_compile_definitions("${PROJECT_NAME}" PRIVATE NDEBUG=1)
endif()
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/santabs"
"${CMAKE_SOURCE_DIR}/Source/common"
)
add_custom_command(TARGET "${PROJECT_NAME}" POST_BUILD
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" "${CMAKE_BINARY_DIR}/Source/santabs/santabs.xpc/Contents/MacOS/santabs"
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" "${CMAKE_BINARY_DIR}/Source/santabs/santabs.xpc"
COMMENT "Signing ${PROJECT_NAME} with the following identity: ${CODESIGN_IDENTITY}"
)
endfunction()
main()

View File

@@ -7,21 +7,21 @@
<key>CFBundleDisplayName</key>
<string>santabs</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<string>santabs</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>com.google.santabs</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<string>santabs</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>${SANTA_VERSION}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>${SANTA_VERSION}</string>
<key>XPCService</key>
<dict>
<key>ServiceType</key>

View File

@@ -49,7 +49,7 @@
// Create listener for return connection from SantaGUI.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[MOLXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
self.listener.unprivilegedInterface = self.listener.privilegedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
self.listener.exportedObject = self;
self.listener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);

View File

@@ -22,7 +22,7 @@
int main(int argc, const char *argv[]) {
MOLXPCConnection *c =
[[MOLXPCConnection alloc] initServerWithListener:[NSXPCListener serviceListener]];
c.exportedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
c.privilegedInterface = c.unprivilegedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
c.exportedObject = [[SNTBundleService alloc] init];
[c resume];
}

View File

@@ -0,0 +1,117 @@
cmake_minimum_required(VERSION 3.10.0)
project(santactl)
function(main)
# Include all files to make IDEs happy
set(PROJECT_SOURCEFILES
SNTCommand.h
SNTCommand.m
SNTCommandController.h
SNTCommandController.m
main.m
Resources/santactl-Info.plist
# Generic commands
Commands/SNTCommandBundleInfo.m
Commands/SNTCommandCheckCache.m
Commands/SNTCommandFileInfo.m
Commands/SNTCommandFlushCache.m
Commands/SNTCommandRule.m
Commands/SNTCommandStatus.m
Commands/SNTCommandVersion.m
Commands/SNTCommandCacheHistogram.m
# Sync server
Commands/sync/NSData+Zlib.h
Commands/sync/NSData+Zlib.m
Commands/sync/SNTCommandSync.m
Commands/sync/SNTCommandSyncConstants.h
Commands/sync/SNTCommandSyncConstants.m
Commands/sync/SNTCommandSyncEventUpload.h
Commands/sync/SNTCommandSyncEventUpload.m
Commands/sync/SNTCommandSyncLogUpload.h
Commands/sync/SNTCommandSyncLogUpload.m
Commands/sync/SNTCommandSyncManager.h
Commands/sync/SNTCommandSyncManager.m
Commands/sync/SNTCommandSyncPostflight.h
Commands/sync/SNTCommandSyncPostflight.m
Commands/sync/SNTCommandSyncPreflight.h
Commands/sync/SNTCommandSyncPreflight.m
Commands/sync/SNTCommandSyncRuleDownload.h
Commands/sync/SNTCommandSyncRuleDownload.m
Commands/sync/SNTCommandSyncStage.h
Commands/sync/SNTCommandSyncStage.m
Commands/sync/SNTCommandSyncState.h
Commands/sync/SNTCommandSyncState.m
# The "common" folder contains some of the files required to build this target
"${CMAKE_SOURCE_DIR}/Source/common/SNTConfigurator.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTFileInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTRule.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTStoredEvent.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTSystemInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCControlInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCUnprivilegedControlInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCSyncdInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTLogging.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTDropRootPrivs.m"
)
add_executable("${PROJECT_NAME}" ${PROJECT_SOURCEFILES})
target_link_libraries("${PROJECT_NAME}" PRIVATE -ObjC)
target_compile_options("${PROJECT_NAME}" PRIVATE
-fobjc-arc -Wno-everything -fmodules
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
target_compile_options("${PROJECT_NAME}" PRIVATE -g3)
endif()
target_compile_definitions("${PROJECT_NAME}" PRIVATE
COCOAPODS=1
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions("${PROJECT_NAME}" PRIVATE DEBUG=1)
else()
target_compile_definitions("${PROJECT_NAME}" PRIVATE NDEBUG=1)
endif()
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/santactl"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands"
"${CMAKE_SOURCE_DIR}/Source/santactl/Commands/sync"
"${CMAKE_SOURCE_DIR}/Source/santad"
"${CMAKE_SOURCE_DIR}/Source/common"
)
target_link_libraries("${PROJECT_NAME}" PRIVATE
MOLCertificate MOLCodesignChecker
MOLAuthenticatingURLSession FMDB MOLFCMClient
MOLXPCConnection
"-framework Cocoa"
)
add_custom_command(TARGET "${PROJECT_NAME}" POST_BUILD
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" $<TARGET_FILE:${PROJECT_NAME}>
COMMENT "Signing ${PROJECT_NAME} with the following identity: ${CODESIGN_IDENTITY}"
)
endfunction()
main()

View File

@@ -22,14 +22,14 @@
#import "SNTStoredEvent.h"
#import "SNTXPCControlInterface.h"
#ifdef DEBUG
@interface SNTCommandBundleInfo : SNTCommand<SNTCommandProtocol>
@end
@implementation SNTCommandBundleInfo
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"bundleinfo")
#endif
+ (BOOL)requiresRoot {
return NO;
@@ -78,3 +78,5 @@ REGISTER_COMMAND_NAME(@"bundleinfo")
}
@end
#endif

View File

@@ -0,0 +1,81 @@
/// Copyright 2018 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.
#ifdef DEBUG
#import <Foundation/Foundation.h>
#import "SNTCommand.h"
#import "SNTCommandController.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTLogging.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandCacheHistogram : SNTCommand<SNTCommandProtocol>
@end
@implementation SNTCommandCacheHistogram
REGISTER_COMMAND_NAME(@"cachehistogram")
+ (BOOL)requiresRoot {
return YES;
}
+ (BOOL)requiresDaemonConn {
return YES;
}
+ (NSString *)shortHelpText {
return @"Print a cache distribution histogram.";
}
+ (NSString *)longHelpText {
return (@"Prints a histogram of each bucket of the in-kernel cache\n"
@" Use -g to get 'graphical' output\n"
@"Only available in DEBUG builds.");
}
- (void)runWithArguments:(NSArray *)arguments {
[[self.daemonConn remoteObjectProxy] cacheBucketCount:^(NSArray *counts) {
NSMutableDictionary<NSNumber *, NSNumber *> *d = [NSMutableDictionary dictionary];
[counts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
d[obj] = @([d[obj] intValue] + 1);
}];
printf("There are %llu empty buckets\n", [d[@0] unsignedLongLongValue]);
for (NSNumber *key in [d.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
if ([key isEqual:@0]) continue;
uint64_t k = [key unsignedLongLongValue];
uint64_t v = [d[key] unsignedLongLongValue];
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"-g"]) {
printf("%4llu: ", k);
for (uint64_t y = 0; y < v; ++y) {
printf("#");
}
printf("\n");
} else {
printf("%4llu: %llu\n", k, v);
}
}
exit(0);
}];
}
@end
#endif

View File

@@ -24,14 +24,14 @@
#include <sys/stat.h>
#ifdef DEBUG
@interface SNTCommandCheckCache : SNTCommand<SNTCommandProtocol>
@end
@implementation SNTCommandCheckCache
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"checkcache")
#endif
+ (BOOL)requiresRoot {
return NO;
@@ -51,7 +51,7 @@ REGISTER_COMMAND_NAME(@"checkcache")
}
- (void)runWithArguments:(NSArray *)arguments {
uint64_t vnodeID = [self vnodeIDForFile:arguments.firstObject];
santa_vnode_id_t vnodeID = [self vnodeIDForFile:arguments.firstObject];
[[self.daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
@@ -67,10 +67,13 @@ REGISTER_COMMAND_NAME(@"checkcache")
}];
}
- (uint64_t)vnodeIDForFile:(NSString *)path {
- (santa_vnode_id_t)vnodeIDForFile:(NSString *)path {
struct stat fstat = {};
stat(path.fileSystemRepresentation, &fstat);
return (((uint64_t)fstat.st_dev << 32) | fstat.st_ino);
santa_vnode_id_t ret = {.fsid = fstat.st_dev, .fileid = fstat.st_ino};
return ret;
}
@end
#endif

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifdef DEBUG
#import <Foundation/Foundation.h>
#import "SNTCommand.h"
@@ -27,9 +29,7 @@
@implementation SNTCommandFlushCache
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"flushcache")
#endif
+ (BOOL)requiresRoot {
return YES;
@@ -61,3 +61,5 @@ REGISTER_COMMAND_NAME(@"flushcache")
}
@end
#endif

View File

@@ -50,10 +50,16 @@ REGISTER_COMMAND_NAME(@"status")
dispatch_group_t group = dispatch_group_create();
// Daemon status
__block BOOL driverConnected;
__block NSString *clientMode;
__block uint64_t cpuEvents, ramEvents;
__block double cpuPeak, ramPeak;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] driverConnectionEstablished:^(BOOL connected) {
driverConnected = connected;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
switch (cm) {
case SNTClientModeMonitor:
@@ -81,11 +87,10 @@ REGISTER_COMMAND_NAME(@"status")
BOOL fileLogging = ([[SNTConfigurator configurator] fileChangesRegex] != nil);
// Kext status
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
__block uint64_t cacheCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] cacheCounts:^(uint64_t rootCache, uint64_t nonRootCache) {
rootCacheCount = rootCache;
nonRootCacheCount = nonRootCache;
[[self.daemonConn remoteObjectProxy] cacheCounts:^(uint64_t count) {
cacheCount = count;
dispatch_group_leave(group);
}];
@@ -160,6 +165,7 @@ REGISTER_COMMAND_NAME(@"status")
if ([arguments containsObject:@"--json"]) {
NSDictionary *stats = @{
@"daemon" : @{
@"driver_connected" : @(driverConnected),
@"mode" : clientMode ?: @"null",
@"file_logging" : @(fileLogging),
@"watchdog_cpu_events" : @(cpuEvents),
@@ -168,8 +174,7 @@ REGISTER_COMMAND_NAME(@"status")
@"watchdog_ram_peak" : @(ramPeak),
},
@"kernel" : @{
@"root_cache_count" : @(rootCacheCount),
@"non_root_cache_count": @(nonRootCacheCount),
@"cache_count" : @(cacheCount),
},
@"database" : @{
@"binary_rules" : @(binaryRuleCount),
@@ -192,13 +197,13 @@ REGISTER_COMMAND_NAME(@"status")
printf("%s\n", [statsStr UTF8String]);
} else {
printf(">>> Daemon Info\n");
printf(" %-25s | %s\n", "Driver Connected", driverConnected ? "Yes" : "No");
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(" %-25s | %lld\n", "Root cache count", rootCacheCount);
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
printf(" %-25s | %lld\n", "Cache count", cacheCount);
printf(">>> Database Info\n");
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);

View File

@@ -37,7 +37,7 @@ REGISTER_COMMAND_NAME(@"sync")
#pragma mark SNTCommand protocol methods
+ (BOOL)requiresRoot {
return NO;
return YES;
}
+ (BOOL)requiresDaemonConn {
@@ -57,6 +57,9 @@ REGISTER_COMMAND_NAME(@"sync")
}
- (void)runWithArguments:(NSArray *)arguments {
// Connect to santad while we are root, so that we pass the XPC authentication
[self.daemonConn resume];
// Ensure we have no privileges
if (!DropRootPrivileges()) {
LOGE(@"Failed to drop root privileges. Exiting.");
@@ -68,7 +71,6 @@ REGISTER_COMMAND_NAME(@"sync")
exit(1);
}
[self.daemonConn resume];
BOOL daemon = [arguments containsObject:@"--daemon"];
self.syncManager = [[SNTCommandSyncManager alloc] initWithDaemonConnection:self.daemonConn
isDaemon:daemon];
@@ -91,7 +93,7 @@ REGISTER_COMMAND_NAME(@"sync")
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[MOLXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCSyncdInterface syncdInterface];
self.listener.privilegedInterface = [SNTXPCSyncdInterface syncdInterface];
self.listener.exportedObject = self.syncManager;
self.listener.acceptedHandler = ^{
LOGD(@"santad <--> santactl connections established");

View File

@@ -15,6 +15,8 @@
#import <Foundation/Foundation.h>
#import "SNTCommandSyncStage.h"
#import "SNTRule.h"
@interface SNTCommandSyncRuleDownload : SNTCommandSyncStage
- (SNTRule *)ruleFromDictionary:(NSDictionary *)dict;
@end

View File

@@ -82,7 +82,11 @@
do {
NSDictionary *requestDict = cursor ? @{kCursor : cursor} : @{};
NSDictionary *response = [self performRequest:[self requestWithDictionary:requestDict]];
if (!response) return nil;
if (![response isKindOfClass:[NSDictionary class]]) {
return nil;
}
for (NSDictionary *ruleDict in response[kRules]) {
SNTRule *rule = [self ruleFromDictionary:ruleDict];
if (rule) [newRules addObject:rule];

View File

@@ -9,11 +9,11 @@
<key>CFBundleIdentifier</key>
<string>com.google.santactl</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<string>santactl</string>
<key>CFBundleShortVersionString</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
<key>CFBundleVersion</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
<key>CSFlags</key>
<string>kill</string>
</dict>

View File

@@ -0,0 +1,114 @@
cmake_minimum_required(VERSION 3.10.0)
project(santad)
function(main)
# Include all files to make IDEs happy
set(PROJECT_SOURCEFILES
SNTApplication.h
SNTApplication.m
SNTCachedDecision.h
SNTCachedDecision.m
SNTDaemonControlController.h
SNTDaemonControlController.m
SNTDatabaseController.h
SNTDatabaseController.m
SNTDriverManager.h
SNTDriverManager.m
SNTExecutionController.h
SNTExecutionController.m
SNTNotificationQueue.h
SNTNotificationQueue.m
SNTPolicyProcessor.h
SNTPolicyProcessor.m
SNTSyncdQueue.h
SNTSyncdQueue.m
DataLayer/SNTDatabaseTable.h
DataLayer/SNTDatabaseTable.m
DataLayer/SNTEventTable.h
DataLayer/SNTEventTable.m
DataLayer/SNTRuleTable.h
DataLayer/SNTRuleTable.m
Logs/SNTEventLog.h
Logs/SNTEventLog.m
Logs/SNTFileEventLog.h
Logs/SNTFileEventLog.m
Logs/SNTSyslogEventLog.h
Logs/SNTSyslogEventLog.m
main.m
Resources/santad-info.plist
# The "common" folder contains some of the files required to build this target
"${CMAKE_SOURCE_DIR}/Source/common/SNTBlockMessage.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTFileInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTConfigurator.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTDropRootPrivs.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTRule.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTStoredEvent.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTSystemInfo.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCBundleServiceInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCControlInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCUnprivilegedControlInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCNotifierInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTXPCSyncdInterface.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTLogging.m"
)
add_executable("${PROJECT_NAME}" ${PROJECT_SOURCEFILES})
target_link_libraries("${PROJECT_NAME}" PRIVATE -ObjC)
target_link_libraries("${PROJECT_NAME}" PRIVATE
MOLCertificate MOLCodesignChecker
FMDB MOLXPCConnection
"-framework Cocoa"
)
target_compile_options("${PROJECT_NAME}" PRIVATE
-fobjc-arc -Wno-everything -fmodules
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
target_compile_options("${PROJECT_NAME}" PRIVATE -g3)
endif()
target_compile_definitions("${PROJECT_NAME}" PRIVATE
COCOAPODS=1
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions("${PROJECT_NAME}" PRIVATE DEBUG=1)
else()
target_compile_definitions("${PROJECT_NAME}" PRIVATE NDEBUG=1)
endif()
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/santad"
"${CMAKE_SOURCE_DIR}/Source/santad/DataLayer"
"${CMAKE_SOURCE_DIR}/Source/santad/Logs"
"${CMAKE_SOURCE_DIR}/Source/common"
)
add_custom_command(TARGET "${PROJECT_NAME}" POST_BUILD
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" $<TARGET_FILE:${PROJECT_NAME}>
COMMENT "Signing ${PROJECT_NAME} with the following identity: ${CODESIGN_IDENTITY}"
)
endfunction()
main()

View File

@@ -50,4 +50,5 @@
// A UTC Date formatter.
@property(readonly, nonatomic) NSDateFormatter *dateFormatter;
@property(readonly, nonatomic) NSString *machineID;
@end

View File

@@ -20,6 +20,7 @@
#include <sys/sysctl.h>
#import "SNTCachedDecision.h"
#import "SNTConfigurator.h"
@interface SNTEventLog ()
@property NSMutableDictionary<NSNumber *, SNTCachedDecision *> *detailStore;
@@ -43,6 +44,9 @@
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
_dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
// Grab the system UUID on init
_machineID = [[SNTConfigurator configurator] machineID];
}
return self;
}
@@ -77,14 +81,14 @@
- (void)cacheDecision:(SNTCachedDecision *)cd {
dispatch_sync(self.detailStoreQueue, ^{
self.detailStore[@(cd.vnodeId)] = cd;
self.detailStore[@(cd.vnodeId.fileid)] = cd;
});
}
- (SNTCachedDecision *)cachedDecisionForMessage:(santa_message_t)message {
__block SNTCachedDecision *cd;
dispatch_sync(self.detailStoreQueue, ^{
cd = self.detailStore[@(message.vnode_id)];
cd = self.detailStore[@(message.vnode_id.fileid)];
});
return cd;
}

View File

@@ -69,6 +69,10 @@
message.uid, [self nameForUID:message.uid],
message.gid, [self nameForGID:message.gid]];
if ([[SNTConfigurator configurator] enableMachineIDDecoration]) {
[outStr appendFormat:@"|machineid=%@", self.machineID];
}
[self writeLog:outStr];
}
@@ -165,6 +169,10 @@
[self addArgsForPid:message.pid toString:outLog];
}
if ([[SNTConfigurator configurator] enableMachineIDDecoration]) {
[outLog appendFormat:@"|machineid=%@", self.machineID];
}
[self writeLog:outLog];
}

View File

@@ -9,10 +9,10 @@
<key>CFBundleIdentifier</key>
<string>com.google.santad</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<string>santad</string>
<key>CFBundleVersion</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>TO.BE.FILLED</string>
<string>${SANTA_VERSION}</string>
</dict>
</plist>

View File

@@ -33,6 +33,7 @@
#import "SNTSyncdQueue.h"
#import "SNTSyslogEventLog.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCUnprivilegedControlInterface.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTApplication ()
@@ -117,7 +118,8 @@
_controlConnection =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]];
_controlConnection.exportedInterface = [SNTXPCControlInterface controlInterface];
_controlConnection.privilegedInterface = [SNTXPCControlInterface controlInterface];
_controlConnection.unprivilegedInterface = [SNTXPCUnprivilegedControlInterface controlInterface];
_controlConnection.exportedObject = dc;
[_controlConnection resume];
@@ -250,7 +252,7 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
if (![props[@"DAVolumeMountable"] boolValue]) return;
[app.eventLog logDiskDisappeared:props];
[app.driverManager flushCacheNonRootOnly:YES];
[app.driverManager flushCache];
}
- (void)startSyncd {
@@ -261,10 +263,9 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
LOGI(@"Failed to fork");
self.syncdPID = 0;
} else if (self.syncdPID == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
// The santactl executable will drop privileges just after the XPC
// connection has been estabilished; this is done this way so that
// the XPC authentication can occur
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
}
LOGI(@"santactl started with pid: %i", self.syncdPID);
@@ -303,7 +304,7 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
if (!new && !old) return;
if (![new.pattern isEqualToString:old.pattern]) {
LOGI(@"Changed [white|black]list regex, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
[self.driverManager flushCache];
}
}
}
@@ -311,7 +312,7 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
- (void)clientModeDidChange:(SNTClientMode)clientMode {
if (clientMode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCacheNonRootOnly:NO];
[self.driverManager flushCache];
}
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:clientMode];
}

View File

@@ -15,13 +15,14 @@
#import <Foundation/Foundation.h>
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
///
/// Store information about executions from decision making for later logging.
///
@interface SNTCachedDecision : NSObject
@property uint64_t vnodeId;
@property santa_vnode_id_t vnodeId;
@property SNTEventState decision;
@property NSString *decisionExtra;
@property NSString *sha256;

View File

@@ -70,19 +70,27 @@ double watchdogRAMPeak = 0;
#pragma mark Kernel ops
- (void)cacheCounts:(void (^)(uint64_t, uint64_t))reply {
NSArray<NSNumber *> *counts = [self.driverManager cacheCounts];
reply([counts[0] unsignedLongLongValue], [counts[1] unsignedLongLongValue]);
- (void)cacheCounts:(void (^)(uint64_t))reply {
uint64_t count = [self.driverManager cacheCount];
reply(count);
}
- (void)cacheBucketCount:(void (^)(NSArray *))reply {
reply([self.driverManager cacheBucketCount]);
}
- (void)flushCache:(void (^)(BOOL))reply {
reply([self.driverManager flushCacheNonRootOnly:NO]);
reply([self.driverManager flushCache]);
}
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply {
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply {
reply([self.driverManager checkCache:vnodeID]);
}
- (void)driverConnectionEstablished:(void (^)(BOOL))reply {
reply(self.driverManager.connectionEstablished);
}
#pragma mark Database ops
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply {
@@ -100,7 +108,7 @@ double watchdogRAMPeak = 0;
NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF.state != %d", SNTRuleStateWhitelist];
if ([rules filteredArrayUsingPredicate:p].count || cleanSlate) {
LOGI(@"Received non-whitelist rule, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
[self.driverManager flushCache];
}
reply(error);

View File

@@ -45,21 +45,30 @@
///
/// Sends a response to a query back to the kernel.
///
- (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeId;
- (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeId;
///
/// Get the number of binaries in the kernel's caches.
///
- (NSArray<NSNumber *> *)cacheCounts;
- (uint64_t)cacheCount;
///
/// Return an array representing all buckets in the kernel's decision cache where each number
/// is the number of items in that bucket.
///
- (NSArray<NSNumber *> *)cacheBucketCount;
///
/// Flush the kernel's binary cache.
///
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly;
- (BOOL)flushCache;
///
/// Check the kernel cache for a VnodeID
///
- (santa_action_t)checkCache:(uint64_t)vnodeID;
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID;
/// Returns whether the connection to the driver has been established.
@property(readonly) BOOL connectionEstablished;
@end

View File

@@ -23,61 +23,27 @@
@interface SNTDriverManager ()
@property io_connect_t connection;
@property(readwrite) BOOL connectionEstablished;
@end
@implementation SNTDriverManager
static const int MAX_DELAY = 15;
#pragma mark init/dealloc
- (instancetype)init {
self = [super init];
if (self) {
kern_return_t kr;
io_service_t serviceObject;
CFDictionaryRef classToMatch;
if (!(classToMatch = IOServiceMatching(USERCLIENT_CLASS))) {
LOGD(@"Failed to create matching dictionary");
CFDictionaryRef classToMatch = IOServiceMatching(USERCLIENT_CLASS);
if (!classToMatch) {
LOGE(@"Failed to create matching dictionary");
return nil;
}
// Attempt to load driver. It may already be running, so ignore any return value.
KextManagerLoadKextWithIdentifier(CFSTR(USERCLIENT_ID), NULL);
// Locate driver. Wait for it if necessary.
int delay = 1;
do {
CFRetain(classToMatch); // this ref is released by IOServiceGetMatchingService
serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch);
if (!serviceObject) {
LOGD(@"Waiting for Santa driver to become available");
sleep(delay);
if (delay < MAX_DELAY) delay *= 2;
}
} while (!serviceObject);
CFRelease(classToMatch);
// This calls `initWithTask`, `attach` and `start` in `SantaDriverClient`
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
IOObjectRelease(serviceObject);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to open Santa driver service");
return nil;
}
// Call `open` in `SantaDriverClient`
kr = IOConnectCallMethod(_connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr == kIOReturnExclusiveAccess) {
LOGD(@"A client is already connected");
return nil;
} else if (kr != kIOReturnSuccess) {
LOGD(@"An error occurred while opening the connection");
return nil;
}
// Wait for the driver to appear
[self waitForDriver:classToMatch];
}
return self;
}
@@ -86,6 +52,71 @@ static const int MAX_DELAY = 15;
IOServiceClose(_connection);
}
#pragma mark Driver Waiting
// Helper function used with IOServiceAddMatchingNotification which expects
// a DriverAppearedBlock to be passed as the 'reference' argument. The block
// will be called for each object iterated over and if the block wants to keep
// the object, it should call IOObjectRetain().
typedef void (^DriverAppearedBlock)(io_object_t object);
static void driverAppearedHandler(void *info, io_iterator_t iterator) {
DriverAppearedBlock block = (__bridge DriverAppearedBlock)info;
if (!block) return;
io_object_t object = 0;
while ((object = IOIteratorNext(iterator))) {
block(object);
IOObjectRelease(object);
}
}
// Wait for the driver to appear, then attach to and open it.
- (void)waitForDriver:(CFDictionaryRef CF_RELEASES_ARGUMENT)matchingDict {
IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop],
IONotificationPortGetRunLoopSource(notificationPort),
kCFRunLoopDefaultMode);
io_iterator_t iterator = 0;
DriverAppearedBlock block = ^(io_object_t object) {
// This calls `initWithTask`, `attach` and `start` in `SantaDriverClient`
kern_return_t kr = IOServiceOpen(object, mach_task_self(), 0, &_connection);
if (kr != kIOReturnSuccess) {
LOGE(@"Failed to open santa-driver service: 0x%X", kr);
exit(1);
}
// Call `open` in `SantaDriverClient`
kr = IOConnectCallMethod(_connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr == kIOReturnExclusiveAccess) {
LOGE(@"A client is already connected");
exit(2);
} else if (kr != kIOReturnSuccess) {
LOGE(@"An error occurred while opening the connection: 0x%X", kr);
exit(3);
}
// Release the iterator to disarm the notifications.
IOObjectRelease(iterator);
IONotificationPortDestroy(notificationPort);
_connectionEstablished = YES;
};
LOGI(@"Waiting for Santa driver to become available");
IOServiceAddMatchingNotification(notificationPort,
kIOMatchedNotification,
matchingDict,
driverAppearedHandler,
(__bridge_retained void *)block,
&iterator);
// Call the handler once to 'empty' the iterator, arming it. If the driver is already loaded
// this will immediately cause the connection to be fully established.
driverAppearedHandler((__bridge_retained void*)block, iterator);
}
#pragma mark Incoming messages
- (void)listenForDecisionRequests:(void (^)(santa_message_t))callback {
@@ -98,7 +129,7 @@ static const int MAX_DELAY = 15;
- (void)listenForRequestsOfType:(santa_queuetype_t)type
withCallback:(void (^)(santa_message_t))callback {
kern_return_t kr;
while (!self.connectionEstablished) usleep(100000); // 100ms
// Allocate a mach port to receive notifactions from the IODataQueue
mach_port_t receivePort = IODataQueueAllocateNotificationPort();
@@ -108,9 +139,9 @@ static const int MAX_DELAY = 15;
}
// This will call registerNotificationPort() inside our user client class
kr = IOConnectSetNotificationPort(self.connection, type, receivePort, 0);
kern_return_t kr = IOConnectSetNotificationPort(self.connection, type, receivePort, 0);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to register notification port for type %d: %d", type, kr);
LOGD(@"Failed to register notification port for type %d: 0x%X", type, kr);
mach_port_destroy(mach_task_self(), receivePort);
return;
}
@@ -120,7 +151,7 @@ static const int MAX_DELAY = 15;
mach_vm_size_t size = 0;
kr = IOConnectMapMemory(self.connection, type, mach_task_self(), &address, &size, kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to map memory for type %d: %d", type, kr);
LOGD(@"Failed to map memory for type %d: 0x%X", type, kr);
mach_port_destroy(mach_task_self(), receivePort);
return;
}
@@ -135,7 +166,7 @@ static const int MAX_DELAY = 15;
if (kr == kIOReturnSuccess) {
callback(vdata);
} else {
LOGE(@"Error dequeuing data for type %d: %d", type, kr);
LOGE(@"Error dequeuing data for type %d: 0x%X", type, kr);
exit(2);
}
}
@@ -147,27 +178,27 @@ static const int MAX_DELAY = 15;
#pragma mark Outgoing messages
- (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeId {
- (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeId {
switch (action) {
case ACTION_RESPOND_ALLOW:
return IOConnectCallScalarMethod(_connection,
return IOConnectCallStructMethod(_connection,
kSantaUserClientAllowBinary,
&vnodeId,
1,
sizeof(vnodeId),
0,
0);
case ACTION_RESPOND_DENY:
return IOConnectCallScalarMethod(_connection,
return IOConnectCallStructMethod(_connection,
kSantaUserClientDenyBinary,
&vnodeId,
1,
sizeof(vnodeId),
0,
0);
case ACTION_RESPOND_ACK:
return IOConnectCallScalarMethod(_connection,
return IOConnectCallStructMethod(_connection,
kSantaUserClientAcknowledgeBinary,
&vnodeId,
1,
sizeof(vnodeId),
0,
0);
default:
@@ -175,9 +206,9 @@ static const int MAX_DELAY = 15;
}
}
- (NSArray<NSNumber *> *)cacheCounts {
uint32_t input_count = 2;
uint64_t cache_counts[2] = {0, 0};
- (uint64_t)cacheCount {
uint32_t input_count = 1;
uint64_t cache_counts[1] = {0};
IOConnectCallScalarMethod(_connection,
kSantaUserClientCacheCount,
@@ -186,30 +217,47 @@ static const int MAX_DELAY = 15;
cache_counts,
&input_count);
return @[ @(cache_counts[0]), @(cache_counts[1]) ];
return cache_counts[0];
}
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly {
const uint64_t nonRoot = nonRootOnly;
- (BOOL)flushCache {
return IOConnectCallScalarMethod(_connection,
kSantaUserClientClearCache,
&nonRoot,
1,
0,
0,
0,
0) == KERN_SUCCESS;
}
- (santa_action_t)checkCache:(uint64_t)vnodeID {
uint32_t input_count = 1;
uint64_t vnode_action = 0;
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
uint64_t output;
uint32_t outputCnt = 1;
IOConnectCallScalarMethod(_connection,
kSantaUserClientCheckCache,
&vnodeID,
1,
&vnode_action,
&input_count);
return (santa_action_t)vnode_action;
IOConnectCallMethod(self.connection, kSantaUserClientCheckCache,
NULL, 0, &vnodeID, sizeof(santa_vnode_id_t),
&output, &outputCnt, NULL, 0);
return (santa_action_t)output;
}
- (NSArray *)cacheBucketCount {
santa_bucket_count_t counts = {};
size_t size = sizeof(counts);
NSMutableArray *a = [NSMutableArray array];
do {
IOConnectCallStructMethod(self.connection,
kSantaUserClientCacheBucketCount,
&counts,
size,
&counts,
&size);
for (uint64_t i = 0; i < sizeof(counts.per_bucket) / sizeof(uint16_t); ++i) {
[a addObject:@(counts.per_bucket[i])];
}
} while (counts.start > 0);
return a;
}
@end

View File

@@ -90,7 +90,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
- (void)validateBinaryWithMessage:(santa_message_t)message {
// Get info about the file. If we can't get this info, allow execution and log an error.
if (unlikely(message.path == NULL)) {
LOGE(@"Path for vnode_id is NULL: %llu", message.vnode_id);
LOGE(@"Path for vnode_id is NULL: %llu/%llu", message.vnode_id.fsid, message.vnode_id.fileid);
[_driverManager postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:message.vnode_id];
return;
}
@@ -115,14 +115,16 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
[_driverManager postToKernelAction:ACTION_RESPOND_ACK forVnodeID:message.vnode_id];
}
// Get codesigning info about the file.
NSError *csError;
MOLCodesignChecker *csInfo =
[[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path
fileDescriptor:binInfo.fileHandle.fileDescriptor
error:&csError];
// Ignore codesigning if there are any errors with the signature.
if (csError) csInfo = nil;
// Get codesigning info about the file but only if it's a Mach-O.
MOLCodesignChecker *csInfo;
if (binInfo.isMachO) {
NSError *csError;
csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path
fileDescriptor:binInfo.fileHandle.fileDescriptor
error:&csError];
// Ignore codesigning if there are any errors with the signature.
if (csError) csInfo = nil;
}
// Actually make the decision.
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo
@@ -139,7 +141,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
if (action == ACTION_RESPOND_ALLOW) [_eventLog cacheDecision:cd];
// Send the decision to the kernel.
[_driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
[_driverManager postToKernelAction:action forVnodeID:message.vnode_id];
// Log to database if necessary.
if (cd.decision != SNTEventStateAllowBinary &&

21
Tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.10.0)
function(main)
file(GLOB test_list LIST_DIRECTORIES true "${CMAKE_CURRENT_SOURCE_DIR}/*")
foreach(test_path ${test_list})
if(NOT EXISTS "${test_path}/CMakeLists.txt")
continue()
endif()
if("${test_path}" STREQUAL "${CMAKE_SOURCE_DIR}/Tests/LogicTests")
message("Not adding LogicTests (not yet supported)")
continue()
endif()
message(STATUS "Adding test folder: ${test_path}")
add_subdirectory("${test_path}")
endforeach()
endfunction()
main()

View File

@@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.10.0)
project(KernelTests)
set(CMAKE_CXX_STANDARD 11)
function(main)
# Include all files to make IDEs happy
set(PROJECT_SOURCEFILES
main.mm
)
add_executable("${PROJECT_NAME}" EXCLUDE_FROM_ALL ${PROJECT_SOURCEFILES})
AddTestTarget("${PROJECT_NAME}" true)
target_compile_options("${PROJECT_NAME}" PRIVATE
-fobjc-arc -Wno-everything -fmodules
-fcxx-modules -mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions("${PROJECT_NAME}" PRIVATE DEBUG=1)
else()
target_compile_definitions("${PROJECT_NAME}" PRIVATE NDEBUG=1)
endif()
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/common"
)
endfunction()
main()

View File

@@ -14,12 +14,14 @@
#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/kext/KextManager.h>
#import <CommonCrypto/CommonDigest.h>
#include <cmath>
#include <ctime>
#include <iostream>
#include <libkern/OSKextLib.h>
#include <mach/mach.h>
#include <numeric>
#include <sys/ptrace.h>
@@ -31,34 +33,41 @@
///
/// Kernel Extension Tests
///
/// Build and launch as root while the kernel extension is loaded and nothing is already connected.
/// Build and launch as root. This target is dependent on the santa-driver target and these
/// tests will load santa-driver from the same location this binary is executed from, unloading
/// any existing driver (and daemon) if necessary.
///
#define TSTART(testName) \
do { printf(" %-50s ", testName); } while (0)
#define TPASS() \
do { printf("\x1b[32mPASS\x1b[0m\n"); } while (0)
do { printf("PASS\n"); } while (0)
#define TPASSINFO(fmt, ...) \
do { printf("\x1b[32mPASS\x1b[0m\n " fmt "\n", ##__VA_ARGS__); } while (0)
do { printf("PASS\n " fmt "\n", ##__VA_ARGS__); } while (0)
#define TFAIL() \
do { \
printf("\x1b[31mFAIL\x1b[0m\n"); \
printf("FAIL\n"); \
[self unloadExtension]; \
exit(1); \
} while (0)
#define TFAILINFO(fmt, ...) \
do { \
printf("\x1b[31mFAIL\x1b[0m\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
[self unloadExtension]; \
exit(1); \
} while (0)
@interface SantaKernelTests : NSObject
@property io_connect_t connection;
@property int timesSeenLs;
@property int timesSeenCat;
@property int timesSeenCp;
@property int testExeIteration;
@property int timesSeenTestExeIteration;
// A block that tests can set to handle specific files/binaries.
// The block should return an action to respond to the kernel with.
// If no block is specified or no action is returned, the exec will be allowed.
@property(atomic, copy) santa_action_t (^handlerBlock)(santa_message_t msg);
- (void)unloadDaemon;
- (void)unloadExtension;
- (void)loadExtension;
- (void)runTests;
@end
@@ -94,21 +103,22 @@
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
/// passing the |vnodeID|.
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeid {
if (action == ACTION_RESPOND_ALLOW) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid, 1, 0, 0);
} else if (action == ACTION_RESPOND_DENY) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid, 1, 0, 0);
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeid {
if (action == ACTION_RESPOND_DENY) {
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
} else if (action == ACTION_RESPOND_ACK) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, 1, 0, 0);
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
} else {
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
}
}
/// Call in-kernel function: |kSantaUserClientClearCache|
- (void)flushCache {
uint64_t nonRootOnly = 0;
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, &nonRootOnly, 1, 0, 0);
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, 0, 0, 0, 0);
}
#pragma mark - Connection Tests
@@ -177,23 +187,16 @@
/// From then on, monitors the IODataQueue and responds for files specifically used in other tests.
/// For everything else, allows execution normally to avoid deadlocking the system.
- (void)beginListening {
kern_return_t kr;
santa_message_t vdata;
UInt32 dataSize;
IODataQueueMemory *queueMemory;
mach_port_t receivePort;
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
TSTART("Allocates a notification port");
mach_port_t receivePort;
if (!(receivePort = IODataQueueAllocateNotificationPort())) {
TFAIL();
}
TPASS();
TSTART("Registers the notification port");
kr = IOConnectSetNotificationPort(self.connection, QUEUETYPE_DECISION, receivePort, 0);
kern_return_t kr = IOConnectSetNotificationPort(
self.connection, QUEUETYPE_DECISION, receivePort, 0);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), receivePort);
TFAILINFO("KR: %d", kr);
@@ -202,6 +205,8 @@
TPASS();
TSTART("Maps shared memory");
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(),
&address, &size, kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
@@ -210,85 +215,26 @@
}
TPASS();
// Fetch the SHA-256 of /bin/ed, as we'll be using that for the cache invalidation test.
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
// Create the RE used for matching testexe's
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
NSString *pattern = [cwd stringByAppendingPathComponent:@"testexe\\.(\\d+)"];
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
/// Begin listening for events
queueMemory = (IODataQueueMemory *)address;
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
do {
while (IODataQueueDataAvailable(queueMemory)) {
dataSize = sizeof(vdata);
santa_message_t vdata;
UInt32 dataSize = sizeof(vdata);
kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize);
if (kr == kIOReturnSuccess) {
if (vdata.action != ACTION_REQUEST_BINARY) continue;
if ([[self sha256ForPath:@(vdata.path)] isEqual:edSHA]) {
[self postToKernelAction:ACTION_RESPOND_DENY forVnodeID:vdata.vnode_id];
} else if (strncmp("/bin/mv", vdata.path, strlen("/bin/mv")) == 0) {
[self postToKernelAction:ACTION_RESPOND_DENY forVnodeID:vdata.vnode_id];
} else if (strncmp("/bin/ls", vdata.path, strlen("/bin/ls")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
self.timesSeenLs++;
} else if (strncmp("/bin/cp", vdata.path, strlen("/bin/cp")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
self.timesSeenCp++;
} else if (strncmp("/bin/cat", vdata.path, strlen("/bin/cat")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
self.timesSeenCat++;
} else if (strncmp("/usr/bin/cal", vdata.path, strlen("/usr/bin/cal")) == 0) {
static int count = 0;
if (count++) TFAILINFO("Large binary should not re-request");
[self postToKernelAction:ACTION_RESPOND_ACK forVnodeID:vdata.vnode_id];
for (int i = 0; i < 15; ++i) {
printf("\033[s"); // save cursor position
printf("%i/15", i);
sleep(1);
printf("\033[u"); // restore cursor position
}
printf("\033[K\033[u"); // clear line, restore cursor position
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
} else if (strncmp("/bin/ln", vdata.path, strlen("/bin/ln")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
TSTART("Sends valid pid/ppid");
if (vdata.pid < 1 || vdata.ppid < 1) {
TFAIL();
}
TPASSINFO("Received pid, ppid: %d, %d", vdata.pid, vdata.ppid);
} else {
NSString *path = @(vdata.path);
// If current executable is one of our test exe's from handlesLotsOfBinaries,
// check that the number has increased.
NSArray *matches = [re matchesInString:path
options:0
range:NSMakeRange(0, path.length)];
if (matches.count == 1 && [matches[0] numberOfRanges] == 2) {
NSUInteger count = [[path substringWithRange:[matches[0] rangeAtIndex:1]] intValue];
if (count <= self.testExeIteration && count > 0) {
self.timesSeenTestExeIteration++;
if (self.timesSeenTestExeIteration > 2) {
TFAILINFO("Saw same binary several times");
}
} else {
self.timesSeenTestExeIteration = 0;
self.testExeIteration = (int)count;
}
}
// Allow everything not related to our testing.
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
}
} else {
if (kr != kIOReturnSuccess) {
TFAILINFO("Error receiving data: %d", kr);
continue;
}
if (vdata.action != ACTION_REQUEST_BINARY) continue;
santa_action_t action = ACTION_RESPOND_ALLOW;
@synchronized(self) {
if (self.handlerBlock) action = self.handlerBlock(vdata);
}
[self postToKernelAction:action forVnodeID:vdata.vnode_id];
}
} while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
@@ -298,12 +244,15 @@
#pragma mark - Functional Tests
/// Tests that blocking works correctly
- (void)receiveAndBlockTests {
TSTART("Blocks denied binaries");
NSTask *ed = [self taskWithPath:@"/bin/ed"];
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/ed", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
return ACTION_RESPOND_ALLOW;
};
NSTask *ed = [self taskWithPath:@"/bin/ed"];
@try {
[ed launch];
[ed waitUntilExit];
@@ -314,17 +263,20 @@
}
}
/// Tests that an allowed binary is cached
- (void)receiveAndCacheTests {
TSTART("Permits & caches allowed binaries");
self.timesSeenLs = 0;
__block int timesSeenLs = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/ls", msg.path, 7) == 0) ++timesSeenLs;
return ACTION_RESPOND_ALLOW;
};
NSTask *ls = [self taskWithPath:@"/bin/ls"];
[ls launch];
[ls waitUntilExit];
if (self.timesSeenLs != 1) {
if (timesSeenLs != 1) {
TFAILINFO("Didn't record first run of ls");
}
@@ -332,25 +284,37 @@
[ls launch];
[ls waitUntilExit];
if (self.timesSeenLs > 1) {
if (timesSeenLs > 1) {
TFAILINFO("Received request for ls a second time");
}
TPASS();
}
/// Tests that a write to a cached vnode will invalidate the cached response for that file
- (void)invalidatesCacheTests {
TSTART("Invalidates cache for manually closed FDs");
// Copy the ls binary to a new file
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
NSString *target =
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Copy the pwd binary to a new file
if (![fm copyItemAtPath:@"/bin/pwd" toPath:target error:nil]) {
TFAILINFO("Failed to create temp file");
}
// Launch the new file to put it in the cache
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
NSTask *pwd = [self taskWithPath:target];
[pwd launch];
[pwd waitUntilExit];
@@ -362,7 +326,7 @@
// Now replace the contents of the test file (which is cached) with the contents of /bin/ed,
// which is 'blacklisted' by SHA-256 during the tests.
FILE *infile = fopen("/bin/ed", "r");
FILE *outfile = fopen("invalidacachetest_tmp", "w");
FILE *outfile = fopen(target.UTF8String, "w");
int ch;
while ((ch = fgetc(infile)) != EOF) {
fputc(ch, outfile);
@@ -370,13 +334,13 @@
fclose(infile);
// Now try running the temp file again. If it succeeds, the test failed.
NSTask *ed = [self taskWithPath:@"invalidacachetest_tmp"];
NSTask *ed = [self taskWithPath:target];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after write while file open");
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
[fm removeItemAtPath:target error:nil];
} @catch (NSException *exception) {
// This is a pass, but we have more to do.
}
@@ -385,7 +349,7 @@
fclose(outfile);
// And try running the temp file again. If it succeeds, the test failed.
ed = [self taskWithPath:@"invalidacachetest_tmp"];
ed = [self taskWithPath:target];
@try {
[ed launch];
@@ -394,14 +358,25 @@
} @catch (NSException *exception) {
TPASS();
} @finally {
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
[fm removeItemAtPath:target error:nil];
}
}
- (void)invalidatesCacheAutoCloseTest {
TSTART("Invalidates cache for auto-closed FDs");
TSTART("Invalidates cache for auto closed FDs");
// Check invalidations when kernel auto-closes descriptors
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if ([[self sha256ForPath:@(msg.path)] isEqual:edSHA]) {
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Create temporary file
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
TFAILINFO("Failed to create temp file");
@@ -411,13 +386,11 @@
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
[pwd launch];
[pwd waitUntilExit];
// Exit if this fails with a useful message.
if ([pwd terminationStatus] != 0) {
TFAILINFO("Second launch of test binary failed");
}
// Replace file contents
// Replace file contents using dd, which doesn't close FDs
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
NSTask *dd = [self taskWithPath:@"/bin/dd"];
dd.arguments = @[ @"if=/bin/ed",
@@ -441,17 +414,20 @@
}
}
/// Tests the clear cache function works correctly
- (void)clearCacheTests {
TSTART("Can clear cache");
self.timesSeenCat = 0;
__block int timesSeenCat = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/cat", msg.path, 8) == 0) ++timesSeenCat;
return ACTION_RESPOND_ALLOW;
};
NSTask *cat = [self taskWithPath:@"/bin/cat"];
[cat launch];
[cat waitUntilExit];
if (self.timesSeenCat != 1) {
if (timesSeenCat != 1) {
TFAILINFO("Didn't record first run of cat");
}
@@ -461,23 +437,27 @@
[cat launch];
[cat waitUntilExit];
if (self.timesSeenCat != 2) {
if (timesSeenCat != 2) {
TFAIL();
}
TPASS();
}
/// Tests that the kernel still denies blocked binaries even if launched while traced
- (void)blocksDeniedTracedBinaries {
TSTART("Denies blocked processes running while traced");
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (strncmp("/bin/mv", msg.path, 7) == 0) return ACTION_RESPOND_DENY;
return ACTION_RESPOND_ALLOW;
};
pid_t pid = fork();
if (pid < 0) {
TFAILINFO("Failed to fork");
} else if (pid > 0) {
int status;
waitpid(pid, &status, 0);
while (waitpid(pid, &status, 0) != pid); // handle EINTR
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
TPASS();
} else if (WIFSTOPPED(status)) {
@@ -494,44 +474,12 @@
}
}
/// Tests that the kernel can handle _lots_ of executions.
- (void)handlesLotsOfBinaries {
TSTART("Handles lots of binaries");
const int LIMIT = 12000;
for (int i = 0; i < LIMIT; ++i) {
printf("\033[s"); // save cursor position
printf("%d/%i", i + 1, LIMIT);
NSString *fname = [@"testexe" stringByAppendingFormat:@".%i", i];
[[NSFileManager defaultManager] copyItemAtPath:@"/bin/hostname" toPath:fname error:NULL];
@try {
NSTask *testexec = [self taskWithPath:fname];
[testexec launch];
[testexec waitUntilExit];
} @catch (NSException *e) {
TFAILINFO("Failed to launch");
}
unlink([fname UTF8String]);
printf("\033[u"); // restore cursor position
}
printf("\033[K\033[u"); // clear line, restore cursor position
TPASS();
}
- (void)testCachePerformance {
TSTART("Test cache performance");
TSTART("Test cache performance...");
// Execute echo 100 times, saving the time taken for each run
std::vector<double> times;
for (int i = 0; i < 100; ++i) {
printf("\033[s"); // save cursor position
printf("%d/%d", i + 1, 100);
NSTask *t = [[NSTask alloc] init];
t.launchPath = @"/bin/echo";
t.standardOutput = [NSPipe pipe];
@@ -542,11 +490,8 @@
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
if (i > 5) times.push_back(duration);
printf("\033[u"); // restore cursor position
}
printf("\033[K\033[u"); // clear line, restore cursor position
// Sort and remove first 10 and last 10 entries.
std::sort(times.begin(), times.end());
times.erase(times.begin(), times.begin()+10);
@@ -570,12 +515,31 @@
}
- (void)testLargeBinary {
TSTART("Handles large binary");
TSTART("Handles large binary...");
__block int calCount = 0;
__weak __typeof(self) weakSelf = self;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
__strong __typeof(weakSelf) self = weakSelf;
if (strncmp("/usr/bin/cal", msg.path, 12) == 0) {
if (calCount++) TFAILINFO("Large binary should not re-request");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
dispatch_get_global_queue(0, 0), ^{
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
});
return ACTION_RESPOND_ACK;
}
return ACTION_RESPOND_ALLOW;
};
@try {
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
[testexec launch];
[testexec waitUntilExit];
int sleepCount = 0;
while ([testexec isRunning]) {
sleep(1);
if (++sleepCount > 5) TFAILINFO("Took longer than expected to start/stop");
}
} @catch (NSException *e) {
TFAILINFO("Failed to launch");
}
@@ -585,8 +549,57 @@
#pragma mark - Main
- (void)unloadDaemon {
NSTask *t = [[NSTask alloc] init];
t.launchPath = @"/bin/launchctl";
t.arguments = @[ @"remove", @"com.google.santad" ];
t.standardOutput = t.standardError = [NSPipe pipe];
[t launch];
[t waitUntilExit];
}
- (void)unloadExtension {
// Don't check the status of this, the kext may not be loaded..
OSStatus ret = KextManagerUnloadKextWithIdentifier(CFSTR("com.google.santa-driver"));
if (ret != kOSReturnSuccess && ret != kOSKextReturnNotFound) {
NSLog(@"Failed to unload extension: 0x%X", ret);
}
}
- (void)loadExtension {
TSTART("Loads extension");
NSError *error;
NSFileManager *fm = [NSFileManager defaultManager];
NSString *src = [[fm currentDirectoryPath] stringByAppendingPathComponent:@"santa-driver.kext"];
NSString *dest = [NSTemporaryDirectory() stringByAppendingPathComponent:@"santa-driver.kext"];
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
if (![fm copyItemAtPath:src toPath:dest error:&error] || error) {
TFAILINFO("Failed to copy kext: %s", error.description.UTF8String);
}
NSDictionary *attrs = @{
NSFileOwnerAccountName : @"root",
NSFileGroupOwnerAccountName : @"wheel",
NSFilePosixPermissions : @0755
};
[fm setAttributes:attrs ofItemAtPath:dest error:NULL];
for (NSString *path in [fm enumeratorAtPath:dest]) {
[fm setAttributes:attrs ofItemAtPath:[dest stringByAppendingPathComponent:path] error:NULL];
}
NSURL *destURL = [NSURL fileURLWithPath:dest];
OSStatus ret = KextManagerLoadKextWithURL((__bridge CFURLRef)destURL, NULL);
if (ret != kOSReturnSuccess) {
TFAILINFO("Failed to load kext: 0x%X", ret);
}
usleep(50000);
TPASS();
}
- (void)runTests {
printf("\nSanta Kernel Tests\n==================\n");
printf("-> Connection tests:\n");
// Test that connection can be established
@@ -598,19 +611,18 @@
// Wait for driver to finish getting ready
sleep(1);
printf("\n-> Functional tests:\033[m\n");
printf("\n-> Functional tests:\n");
[self receiveAndBlockTests];
[self receiveAndCacheTests];
[self invalidatesCacheTests];
[self invalidatesCacheAutoCloseTest];
[self clearCacheTests];
[self blocksDeniedTracedBinaries];
printf("\n-> Performance tests:\033[m\n");
[self testCachePerformance];
[self testLargeBinary];
[self handlesLotsOfBinaries];
printf("\n-> Performance tests:\n");
[self testCachePerformance];
printf("\nAll tests passed.\n\n");
}
@@ -626,8 +638,18 @@ int main(int argc, const char *argv[]) {
exit(1);
}
chdir([[[NSBundle mainBundle] bundlePath] UTF8String]);
SantaKernelTests *skt = [[SantaKernelTests alloc] init];
printf("\nSanta Kernel Tests\n==================\n\n");
printf("-> Loading tests:\n");
[skt unloadDaemon];
[skt unloadExtension];
[skt loadExtension];
printf("\n");
[skt runTests];
[skt unloadExtension];
}
return 0;
}

View File

@@ -80,11 +80,15 @@
santa_message_t message = {0};
message.pid = 12;
message.ppid = 1;
message.vnode_id = 1234;
message.vnode_id = [self getVnodeId];
strncpy(message.path, "/a/file", 7);
return message;
}
- (santa_vnode_id_t)getVnodeId {
return (santa_vnode_id_t){.fsid = 1234, .fileid = 5678};
}
- (void)tearDown {
[self.mockFileInfo stopMocking];
[self.mockCodesignChecker stopMocking];
@@ -107,7 +111,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
- (void)testBinaryBlacklistRule {
@@ -122,7 +126,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_DENY
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
- (void)testCertificateWhitelistRule {
@@ -140,7 +144,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
- (void)testCertificateBlacklistRule {
@@ -158,7 +162,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_DENY
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
- (void)testDefaultDecision {
@@ -168,12 +172,12 @@
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_DENY
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
- (void)testOutOfScope {
@@ -182,13 +186,13 @@
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
- (void)testMissingShasum {
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
- (void)testPageZero {
@@ -197,7 +201,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_DENY
forVnodeID:1234]);
forVnodeID:[self getVnodeId]]);
}
@end

View File

@@ -14,7 +14,9 @@
#import <XCTest/XCTest.h>
#include <numeric>
#include <string>
#include <vector>
#include "SantaCache.h"
@@ -28,71 +30,113 @@
}
- (void)testSetAndGet {
auto sut = new SantaCache<uint64_t>();
auto sut = SantaCache<uint64_t, uint64_t>();
sut->set(72057611258548992llu, 10000192);
XCTAssertEqual(sut->get(72057611258548992llu), 10000192);
delete sut;
sut.set(72057611258548992llu, 10000192);
XCTAssertEqual(sut.get(72057611258548992llu), 10000192);
}
- (void)testCacheRemove {
auto sut = new SantaCache<uint64_t>();
auto sut = SantaCache<uint64_t, uint64_t>();
sut->set(0xDEADBEEF, 42);
sut->remove(0xDEADBEEF);
sut.set(0xDEADBEEF, 42);
sut.remove(0xDEADBEEF);
XCTAssertEqual(sut->get(0xDEADBEEF), 0);
delete sut;
XCTAssertEqual(sut.get(0xDEADBEEF), 0);
}
- (void)testBucketGrowCopy {
auto sut = new SantaCache<uint64_t>();
auto sut = SantaCache<uint64_t, uint64_t>();
sut->set(386, 42);
sut->set(2434, 42);
sut.set(386, 42);
sut.set(2434, 42);
XCTAssertEqual(sut->get(386), 42);
XCTAssertEqual(sut->get(2434), 42);
delete sut;
XCTAssertEqual(sut.get(386), 42);
XCTAssertEqual(sut.get(2434), 42);
}
- (void)testBucketShrinkCopy {
auto sut = new SantaCache<uint64_t>(100, 1);
auto sut = SantaCache<uint64_t, uint64_t>(100, 1);
sut->set(386, 42);
sut->set(2434, 42);
sut->set(4482, 42);
sut.set(386, 42);
sut.set(2434, 42);
sut.set(4482, 42);
sut->remove(2434);
sut.remove(2434);
XCTAssertEqual(sut->get(386), 42);
XCTAssertEqual(sut->get(2434), 0);
XCTAssertEqual(sut->get(4482), 42);
delete sut;
XCTAssertEqual(sut.get(386), 42);
XCTAssertEqual(sut.get(2434), 0);
XCTAssertEqual(sut.get(4482), 42);
}
- (void)testCacheResetAtLimit {
auto sut = new SantaCache<uint64_t>(5);
auto sut = SantaCache<uint64_t, uint64_t>(5);
sut->set(1, 42);
sut->set(2, 42);
sut->set(3, 42);
sut->set(4, 42);
sut->set(5, 42);
XCTAssertEqual(sut->get(3), 42);
sut->set(6, 42);
XCTAssertEqual(sut->get(3), 0);
XCTAssertEqual(sut->get(6), 42);
sut.set(1, 42);
sut.set(2, 42);
sut.set(3, 42);
sut.set(4, 42);
sut.set(5, 42);
XCTAssertEqual(sut.get(3), 42);
sut.set(6, 42);
XCTAssertEqual(sut.get(3), 0);
XCTAssertEqual(sut.get(6), 42);
}
delete sut;
// Helper to test bucket distributions for uint64_t/uint64_t combinations.
- (void)distributionTestHelper:(SantaCache<uint64_t, uint64_t> *)sut bucketRatio:(int)br {
uint16_t count[512];
uint16_t array_size = 512;
uint64_t start_bucket = 0;
std::vector<uint16_t> per_bucket;
do {
sut->bucket_counts(count, &array_size, &start_bucket);
for (int i = 0; i < array_size; ++i) {
per_bucket.push_back(count[i]);
}
} while (start_bucket > 0);
// Calculate mean
double mean = std::accumulate(per_bucket.begin(), per_bucket.end(), 0.0) / per_bucket.size();
XCTAssertLessThanOrEqual(mean, br, @"Mean per-bucket count is greater than %d", br);
// Calculate stdev
double accum = 0.0;
std::for_each(per_bucket.begin(), per_bucket.end(), [&](const double d) {
accum += (d - mean) * (d - mean);
});
double stddev = sqrt(accum / (per_bucket.size() - 1));
double maxStdDev = (double)br / 2;
XCTAssertLessThanOrEqual(stddev, maxStdDev,
@"Standard deviation between buckets is greater than %f", maxStdDev);
}
- (void)testDistributionRandomKeys {
const int bucket_ratio = 5;
auto sut = new SantaCache<uint64_t, uint64_t>(5000, bucket_ratio);
// Fill the cache with random keys, all set to 1.
for (int i = 0; i < 4000; ++i) {
sut->set((uint64_t)arc4random() << 32 | arc4random(), 1);
}
[self distributionTestHelper:sut bucketRatio:bucket_ratio];
}
- (void)testDistributionMontonicKeys {
const int bucket_ratio = 5;
auto sut = new SantaCache<uint64_t, uint64_t>(5000, bucket_ratio);
// Fill the cache with monotonic keys, all set to 1.
for (int i = 0; i < 4000; ++i) {
sut->set(i, 1);
}
[self distributionTestHelper:sut bucketRatio:bucket_ratio];
}
- (void)testThreading {
auto sut = new SantaCache<uint64_t>();
auto sut = new SantaCache<uint64_t, uint64_t>();
for (int x = 0; x < 200; ++x) {
dispatch_group_t group = dispatch_group_create();
@@ -120,57 +164,105 @@
}
- (void)testCount {
auto sut = new SantaCache<uint64_t>();
auto sut = SantaCache<uint64_t, int>();
XCTAssertEqual(sut->count(), 0);
XCTAssertEqual(sut.count(), 0);
sut->set(4012, 42);
sut->set(42, 0);
sut->set(0x8BADF00D, 40010);
sut.set(4012, 42);
sut.set(42, 0);
sut.set(0x8BADF00D, 40010);
XCTAssertEqual(sut->count(), 2);
XCTAssertEqual(sut.count(), 2);
}
delete sut;
- (void)testDoubles {
auto sut = SantaCache<double, double>();
sut.set(3.14, 2.718281);
sut.set(1.41429, 2.5029);
sut.set(4.6692, 1.2020569);
sut.set(1.61803398, 0.57721);
XCTAssertEqual(sut.count(), 4);
XCTAssertEqual(sut.get(3.14), 2.718281);
XCTAssertEqual(sut.get(1.41429), 2.5029);
XCTAssertEqual(sut.get(4.6692), 1.2020569);
XCTAssertEqual(sut.get(1.61803398), 0.57721);
XCTAssertEqual(sut.get(5.5555), 0);
XCTAssertEqual(sut.get(3.1459124), 0);
}
template<> uint64_t SantaCacheHasher<std::string>(std::string const& s) {
return std::hash<std::string>{}(s);
}
- (void)testStrings {
auto sut = new SantaCache<std::string>();
auto sut = SantaCache<std::string, std::string>();
sut->set(1, "deadbeef");
sut->set(2, "feedface");
std::string s1 = "foo";
std::string s2 = "bar";
XCTAssertEqual(sut->count(), 2);
XCTAssertEqual(sut->get(1), "deadbeef");
XCTAssertEqual(sut->get(2), "feedface");
sut.set(s1, "deadbeef");
sut.set(s2, "feedface");
sut->remove(2);
XCTAssertEqual(sut.count(), 2);
XCTAssertEqual(sut.get(s1), "deadbeef");
XCTAssertEqual(sut.get(s2), "feedface");
XCTAssertTrue(sut->get(2).empty());
sut.remove(s2);
delete sut;
XCTAssertTrue(sut.get(s2).empty());
}
- (void)testCompareAndSwap {
auto sut = new SantaCache<uint64_t>(100, 2);
auto sut = SantaCache<uint64_t, uint64_t>(100, 2);
sut->set(1, 42);
sut->set(1, 666, 1);
sut->set(1, 666, 0);
XCTAssertEqual(sut->get(1), 42);
sut.set(1, 42);
sut.set(1, 666, 1);
sut.set(1, 666, 0);
XCTAssertEqual(sut.get(1), 42);
sut->set(1, 0);
XCTAssertEqual(sut->get(1), 0);
sut.set(1, 0);
XCTAssertEqual(sut.get(1), 0);
sut->set(1, 42, 1);
XCTAssertEqual(sut->get(1), 0);
sut.set(1, 42, 1);
XCTAssertEqual(sut.get(1), 0);
sut->set(1, 42, 0);
XCTAssertEqual(sut->get(1), 42);
sut.set(1, 42, 0);
XCTAssertEqual(sut.get(1), 42);
sut->set(1, 0, 666);
XCTAssertEqual(sut->get(1), 42);
sut->set(1, 0, 42);
XCTAssertEqual(sut->get(1), 0);
sut.set(1, 0, 666);
XCTAssertEqual(sut.get(1), 42);
sut.set(1, 0, 42);
XCTAssertEqual(sut.get(1), 0);
}
struct S {
uint64_t first_val;
uint64_t second_val;
bool operator==(const S& rhs) {
return first_val == rhs.first_val && second_val == rhs.second_val;
}
};
template<> uint64_t SantaCacheHasher<S>(S const& s) {
return SantaCacheHasher<uint64_t>(s.first_val) ^ (SantaCacheHasher<uint64_t>(s.second_val) << 1);
}
- (void)testStructKeys {
auto sut = SantaCache<S, uint64_t>(10, 2);
S s1 = {1024, 2048};
S s2 = {4096, 8192};
S s3 = {16384, 32768};
sut.set(s1, 10);
sut.set(s2, 20);
sut.set(s3, 30);
XCTAssertEqual(sut.get(s1), 10);
XCTAssertEqual(sut.get(s2), 20);
XCTAssertEqual(sut.get(s3), 30);
}
@end

View File

@@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.10.0)
project(santa-driver-test)
function(main)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_GNU_EXTENSIONS OFF)
# Include all files to make IDEs happy
set(PROJECT_SOURCEFILES
main.mm
"${CMAKE_SOURCE_DIR}/Source/santad/SNTDriverManager.h"
"${CMAKE_SOURCE_DIR}/Source/santad/SNTDriverManager.m"
"${CMAKE_SOURCE_DIR}/Source/common/SNTLogging.m"
)
add_executable("${PROJECT_NAME}" EXCLUDE_FROM_ALL ${PROJECT_SOURCEFILES})
AddTestTarget("${PROJECT_NAME}" true)
target_link_libraries("${PROJECT_NAME}" PRIVATE
-ObjC "-framework Cocoa"
)
target_compile_options("${PROJECT_NAME}" PRIVATE
-fobjc-arc -Wno-everything -fmodules -fcxx-modules
-mmacosx-version-min=${MACOSX_VERSION_MIN}
)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
target_compile_options("${PROJECT_NAME}" PRIVATE -g3)
endif()
target_compile_definitions("${PROJECT_NAME}" PRIVATE
COCOAPODS=1
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions("${PROJECT_NAME}" PRIVATE DEBUG=1)
else()
target_compile_definitions("${PROJECT_NAME}" PRIVATE NDEBUG=1)
endif()
target_include_directories("${PROJECT_NAME}" PRIVATE
"${CMAKE_SOURCE_DIR}/Source/santad"
"${CMAKE_SOURCE_DIR}/Source/common"
)
add_custom_command(TARGET "${PROJECT_NAME}" POST_BUILD
COMMAND codesign --force --verify --verbose --sign "${CODESIGN_IDENTITY}" $<TARGET_FILE:${PROJECT_NAME}>
COMMENT "Signing ${PROJECT_NAME} with the following identity: ${CODESIGN_IDENTITY}"
)
endfunction()
main()

View File

@@ -0,0 +1,70 @@
/// Copyright 2018 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.
/*
This test will attempt to connect to the driver multiple times to make
sure that it doesn't cause crashes
*/
#import "SNTDriverManager.h"
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <vector>
#include <atomic>
std::mutex mutex;
std::condition_variable condVariable;
std::atomic_bool terminate(false);
void SignalHandler(int signalId) {
std::lock_guard<std::mutex> lock(mutex);
terminate = true;
condVariable.notify_one();
}
void Thread() {
while (!terminate) {
SNTDriverManager *driverManager = [[SNTDriverManager alloc] init];
if (driverManager != nullptr) {
std::cout << ".";
}
}
}
int main(int argc, const char *argv[]) {
signal(SIGINT, &SignalHandler);
std::vector<std::thread> thread_list;
for (auto i = 0U; i < 1U; i++) {
thread_list.emplace_back(Thread);
}
std::unique_lock<std::mutex> lock(mutex);
condVariable.wait(
lock,
[]{
return terminate.load();
}
);
for (auto &t : thread_list) {
t.join();
}
return 0;
}