Compare commits

...

9 Commits

Author SHA1 Message Date
Tom Burgin
2d98173c51 fix cache invalidation on macOS Mojave (#298) 2018-09-21 15:22:34 -04:00
Tom Burgin
5e3f13be70 intentional fall-through (#297)
* intentional fall-through

* russell's idea
2018-09-20 18:40:23 -04:00
Tom Burgin
90b894b88a santad: add critical system binaries (#296)
* santad: add critical system binaries

* review updates

* use a getter
2018-09-20 17:17:12 -04:00
nguyen-phillip
6dc7387881 Add transitive whitelisting to Santa (#224)
Add transitive whitelisting.

Binaries may be identified with WHITELIST_COMPILER rules.  Any executable they output will then be marked locally with a transitive whitelist rule and allowed to run if the TransitiveWhitelistingEnabled config key is true.
2018-07-20 11:47:04 -04:00
Tom Burgin
b14b017d72 santa-driver: add IOMatchCategory (#292) 2018-07-18 11:33:09 -04:00
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
84 changed files with 3262 additions and 246 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

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

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,14 +4,14 @@ 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):
- MOLFCMClient (1.8):
- MOLAuthenticatingURLSession (~> 2.4)
- MOLXPCConnection (1.1):
- MOLXPCConnection (1.2):
- MOLCodesignChecker (~> 1.9)
- OCMock (3.4.1)
- OCMock (3.4.2)
DEPENDENCIES:
- FMDB
@@ -35,11 +35,11 @@ SPEC REPOS:
SPEC CHECKSUMS:
FMDB: 6198a90e7b6900cfc046e6bc0ef6ebb7be9236aa
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
MOLCertificate: c999513316d511c69f290fbf313dfe8dca4ad592
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
MOLFCMClient: ee45348909351f232e2759c580329072ae7e02d4
MOLXPCConnection: de9d5535928f59766a768384e411077b83ec2f9c
OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3
MOLFCMClient: 2bfbacd45cc11e1ca3c077e97b80401c4e4a54f1
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
OCMock: ebe9ee1dca7fbed0ff9193ac0b3e2d8862ea56f6
PODFILE CHECKSUM: ddca043a7ace9ec600c108621c56d13a50d17236

View File

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

@@ -149,6 +149,8 @@
59D56CF2D9C5BD9B7E3CC56D /* libPods-santad-santabs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14B98F4051188ECB7D024331 /* libPods-santad-santabs.a */; };
81133DB01F3A76F700917FF9 /* SNTCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 81133DAF1F3A75CE00917FF9 /* SNTCommand.m */; };
81133DB11F3A77C600917FF9 /* SNTCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 81133DAF1F3A75CE00917FF9 /* SNTCommand.m */; };
81A00E7F1FD74F8E00A84676 /* SNTCompilerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81A00E7E1FD74EFF00A84676 /* SNTCompilerController.m */; };
81A00E801FD74F9100A84676 /* SNTCompilerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81A00E7E1FD74EFF00A84676 /* SNTCompilerController.m */; };
B352A545B76783D568A6D0C5 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90E9D568200AB9B642E06272 /* libPods-Santa.a */; };
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */; };
C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; };
@@ -185,6 +187,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 */
@@ -420,6 +427,8 @@
7D949AA996AEAC326A4F6596 /* libPods-LogicTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LogicTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
81133DAE1F3A75CE00917FF9 /* SNTCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommand.h; sourceTree = "<group>"; };
81133DAF1F3A75CE00917FF9 /* SNTCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommand.m; sourceTree = "<group>"; };
81A00E7D1FD74EFF00A84676 /* SNTCompilerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCompilerController.h; sourceTree = "<group>"; };
81A00E7E1FD74EFF00A84676 /* SNTCompilerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCompilerController.m; sourceTree = "<group>"; };
8EF10E4B8C86CED022C72F1B /* Pods-santactl.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santactl.debug.xcconfig"; path = "Pods/Target Support Files/Pods-santactl/Pods-santactl.debug.xcconfig"; sourceTree = "<group>"; };
90E9D568200AB9B642E06272 /* libPods-Santa.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Santa.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A6A91785C40257CC156B4F05 /* Pods-Santa.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.release.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.release.xcconfig"; sourceTree = "<group>"; };
@@ -450,6 +459,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>"; };
@@ -745,6 +756,8 @@
0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */,
C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */,
C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */,
D221000F20AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.h */,
D221001020AB35F3003C65C2 /* SNTXPCUnprivilegedControlInterface.m */,
);
path = common;
sourceTree = "<group>";
@@ -759,6 +772,8 @@
0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */,
0DE71A731B95F7F900518526 /* SNTCachedDecision.h */,
0DE71A741B95F7F900518526 /* SNTCachedDecision.m */,
81A00E7D1FD74EFF00A84676 /* SNTCompilerController.h */,
81A00E7E1FD74EFF00A84676 /* SNTCompilerController.m */,
0D8E18CB19107B56000F89B8 /* SNTDaemonControlController.h */,
0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */,
0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */,
@@ -1371,10 +1386,12 @@
0D41DAD41A7C28C800A890FE /* SNTEventTableTest.m in Sources */,
0D3AFBEE18FB4C6C0087BCEE /* SNTApplication.m in Sources */,
0DD0D48F194F78F8005F27EB /* SNTFileInfoTest.m in Sources */,
81A00E801FD74F9100A84676 /* SNTCompilerController.m in Sources */,
0DC5D86E191AED220078A5C0 /* SNTRuleTable.m in Sources */,
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 */,
@@ -1403,6 +1420,7 @@
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 */,
@@ -1429,6 +1447,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 */,
@@ -1467,6 +1486,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 */,
@@ -1477,6 +1497,7 @@
0D377C2A17A071B7008453DB /* SNTEventTable.m in Sources */,
0DE50F681912716A007B2B0C /* SNTRule.m in Sources */,
0DB77FD81CCE824A004DF060 /* SNTBlockMessage.m in Sources */,
81A00E7F1FD74F8E00A84676 /* SNTCompilerController.m in Sources */,
0D37C10F18F6029A0069BC61 /* SNTDatabaseTable.m in Sources */,
C748E8A720696595006CFD1B /* SNTFileEventLog.m in Sources */,
C748E8A3206964E1006CFD1B /* SNTEventLog.m in Sources */,
@@ -1497,6 +1518,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 */,

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

@@ -33,6 +33,9 @@ typedef NS_ENUM(NSInteger, SNTRuleState) {
SNTRuleStateBlacklist = 2,
SNTRuleStateSilentBlacklist = 3,
SNTRuleStateRemove = 4,
SNTRuleStateWhitelistCompiler = 5,
SNTRuleStateWhitelistTransitive = 6,
};
typedef NS_ENUM(NSInteger, SNTClientMode) {
@@ -58,6 +61,9 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateAllowBinary = 1 << 25,
SNTEventStateAllowCertificate = 1 << 26,
SNTEventStateAllowScope = 1 << 27,
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,

View File

@@ -198,6 +198,15 @@
///
@property BOOL bundlesEnabled;
#pragma mark Transitive Whitelisting Settings
///
/// If YES, binaries marked with SNTRuleStateWhitelistCompiler rules are allowed to transitively
/// whitelist any executables that they produce. If NO, SNTRuleStateWhitelistCompiler rules are
/// interpreted as if they were simply SNTRuleStateWhitelist rules. Defaults to NO.
///
@property BOOL transitiveWhitelistingEnabled;
#pragma mark Server Auth Settings
///

View File

@@ -76,6 +76,7 @@ static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration"
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kTransitiveWhitelistingEnabledKey = @"TransitiveWhitelistingEnabled";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
@@ -94,6 +95,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
Class data = [NSData class];
_syncServerKeyTypes = @{
kClientModeKey : number,
kTransitiveWhitelistingEnabledKey : number,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kFullSyncLastSuccess : date,
@@ -102,6 +104,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
kTransitiveWhitelistingEnabledKey : number,
kFileChangesRegexKey : re,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
@@ -287,6 +290,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingTransitiveWhitelistingEnabled {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -311,6 +318,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
- (BOOL)transitiveWhitelistingEnabled {
return [self.configState[kTransitiveWhitelistingEnabledKey] boolValue];
}
- (void)setTransitiveWhitelistingEnabled:(BOOL)enabled {
[self updateSyncStateForKey:kTransitiveWhitelistingEnabledKey value:@(enabled)];
}
- (NSRegularExpression *)whitelistPathRegex {
return self.syncState[kWhitelistRegexKey] ?: self.configState[kWhitelistRegexKey];
}

View File

@@ -33,9 +33,11 @@
enum SantaDriverMethods {
kSantaUserClientOpen,
kSantaUserClientAllowBinary,
kSantaUserClientAllowCompiler,
kSantaUserClientDenyBinary,
kSantaUserClientAcknowledgeBinary,
kSantaUserClientClearCache,
kSantaUserClientRemoveCacheEntry,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
kSantaUserClientCacheBucketCount,
@@ -47,7 +49,7 @@ enum SantaDriverMethods {
typedef enum {
QUEUETYPE_DECISION,
QUEUETYPE_LOG
QUEUETYPE_LOG,
} santa_queuetype_t;
// Enum defining actions that can be passed down the IODataQueue and in
@@ -64,6 +66,10 @@ typedef enum {
ACTION_RESPOND_DENY = 21,
ACTION_RESPOND_TOOLONG = 22,
ACTION_RESPOND_ACK = 23,
ACTION_RESPOND_ALLOW_COMPILER = 24,
// The following response is stored only in the kernel decision cache.
// It is removed by SNTCompilerController
ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE = 25,
// NOTIFY
ACTION_NOTIFY_EXEC = 30,
@@ -72,13 +78,17 @@ typedef enum {
ACTION_NOTIFY_LINK = 33,
ACTION_NOTIFY_EXCHANGE = 34,
ACTION_NOTIFY_DELETE = 35,
ACTION_NOTIFY_WHITELIST = 36,
// ERROR
ACTION_ERROR = 99,
} santa_action_t;
#define RESPONSE_VALID(x) \
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY)
(x == ACTION_RESPOND_ALLOW || \
x == ACTION_RESPOND_DENY || \
x == ACTION_RESPOND_ALLOW_COMPILER || \
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
// Struct to manage vnode IDs
typedef struct santa_vnode_id_t {

View File

@@ -41,12 +41,32 @@
///
@property(copy) NSString *customMsg;
///
/// The time when this rule was last retrieved from the rules database, if rule is transitive.
/// Stored as number of seconds since 00:00:00 UTC on 1 January 2001.
///
@property(readonly) NSUInteger timestamp;
///
/// Designated initializer.
///
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp;
///
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
///
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;
///
/// Sets timestamp of rule to the current time.
///
- (void)resetTimestamp;
@end

View File

@@ -14,22 +14,45 @@
#import "SNTRule.h"
@interface SNTRule()
@property(readwrite) NSUInteger timestamp;
@end
@implementation SNTRule
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
self = [super init];
if (self) {
_shasum = shasum;
_state = state;
_type = type;
_customMsg = customMsg;
_timestamp = timestamp;
}
return self;
}
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithShasum:shasum
state:state
type:type
customMsg:customMsg
timestamp:0];
// Initialize timestamp to current time if rule is transitive.
if (self && state == SNTRuleStateWhitelistTransitive) {
[self resetTimestamp];
}
return self;
}
#pragma mark NSSecureCoding
#pragma clang diagnostic push
@@ -46,6 +69,7 @@
ENCODE(@(self.state), @"state");
ENCODE(@(self.type), @"type");
ENCODE(self.customMsg, @"custommsg");
ENCODE(@(self.timestamp), @"timestamp");
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
@@ -55,6 +79,7 @@
_state = [DECODE(NSNumber, @"state") intValue];
_type = [DECODE(NSNumber, @"type") intValue];
_customMsg = DECODE(NSString, @"custommsg");
_timestamp = [DECODE(NSNumber, @"timestamp") unsignedIntegerValue];
}
return self;
}
@@ -80,8 +105,14 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
self.shasum, self.state, self.type];
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
}
# pragma mark Last-access Timestamp
- (void)resetTimestamp {
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];
}
@end

View File

@@ -12,120 +12,57 @@
/// 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 count))reply;
- (void)flushCache:(void (^)(BOOL))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)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;
- (void)setTransitiveWhitelistingEnabled:(BOOL)enabled reply:(void (^)(void))reply;
///
/// 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,123 @@
/// 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,
int64_t compiler,
int64_t transitive))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;
- (void)transitiveWhitelistingEnabled:(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>
@@ -32,6 +32,8 @@
<string>IOKit</string>
<key>IOUserClientClass</key>
<string>com_google_SantaDriverClient</string>
<key>IOMatchCategory</key>
<string>com_google_SantaDriver</string>
</dict>
</dict>
<key>OSBundleLibraries</key>

View File

@@ -115,8 +115,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
@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 key The key.
@param value The value with parameterized type.
@return true if the value was set.
*/
@@ -130,9 +130,9 @@ template<typename KeyT, typename ValueT> class SantaCache {
@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
@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.
@@ -236,12 +236,12 @@ template<typename KeyT, typename ValueT> class SantaCache {
@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
@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.
@param has_prev_value Pass true if previous_value should be used.
@return true if the entry was set, false if it was not
*/

View File

@@ -14,6 +14,14 @@
#include "SantaDecisionManager.h"
// This is a made-up KAUTH_FILEOP constant which represents a
// KAUTH_VNODE_WRITE_DATA event that gets passed to SantaDecisionManager's
// FileOpCallback method. The KAUTH_FILEOP_* constants are defined in
// sys/kauth.h and run from 1--7. KAUTH_VNODE_WRITE_DATA is already defined as
// 4 so it overlaps with the other KAUTH_FILEOP_* constants and can't be used.
// We define KAUTH_FILEOP_WRITE as something much greater than 7.
#define KAUTH_FILEOP_WRITE 100
#define super OSObject
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
@@ -35,6 +43,7 @@ bool SantaDecisionManager::init() {
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);
compiler_pid_set_ = new SantaCache<pid_t, pid_t>(500, 5);
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
@@ -53,6 +62,8 @@ void SantaDecisionManager::free() {
delete decision_cache_;
delete vnode_pid_map_;
StopPidMonitorThreads();
if (decision_dataqueue_lock_) {
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
decision_dataqueue_lock_ = nullptr;
@@ -195,6 +206,90 @@ kern_return_t SantaDecisionManager::StopListener() {
return kIOReturnSuccess;
}
# pragma mark Monitoring PIDs
// Arguments that are passed to pid_monitor thread.
typedef struct {
pid_t pid; // process to monitor
SantaDecisionManager *sdm; // reference to SantaDecisionManager
} pid_monitor_info;
// Function executed in its own thread used to monitor a compiler process for
// termination and then remove the process pid from cache of compiler pids.
static void pid_monitor(void *param, __unused wait_result_t wait_result) {
pid_monitor_info *info = (pid_monitor_info *)param;
if (info && info->sdm) {
uint32_t sleep_time = info->sdm->PidMonitorSleepTimeMilliseconds();
while (!info->sdm->PidMonitorThreadsShouldExit()) {
proc_t proc = proc_find(info->pid);
if (!proc) break;
proc_rele(proc);
IOSleep(sleep_time);
}
info->sdm->ForgetCompilerPid(info->pid);
info->sdm->DecrementPidMonitorThreadCount();
}
thread_terminate(current_thread());
}
// TODO(nguyenphillip): Look at moving pid monitoring out of SDM entirely,
// maybe by creating a dedicated class to do this that SDM could then query.
void SantaDecisionManager::MonitorCompilerPidForExit(pid_t pid) {
// Don't start any new threads if compiler_pid_set_ doesn't exist.
if (!compiler_pid_set_) return;
auto info = new pid_monitor_info;
info->pid = pid;
info->sdm = this;
thread_t thread = THREAD_NULL;
IncrementPidMonitorThreadCount();
if (KERN_SUCCESS != kernel_thread_start(pid_monitor, (void *)info, &thread)) {
LOGE("couldn't start pid monitor thread");
DecrementPidMonitorThreadCount();
}
thread_deallocate(thread);
}
void SantaDecisionManager::ForgetCompilerPid(pid_t pid) {
if (compiler_pid_set_) compiler_pid_set_->remove(pid);
}
bool SantaDecisionManager::PidMonitorThreadsShouldExit() const {
return compiler_pid_set_ == nullptr;
}
bool SantaDecisionManager::StopPidMonitorThreads() {
// Each pid_monitor thread checks for the existence of compiler_pid_set_.
// As soon as they see that it's gone, they should terminate and decrement
// SantaDecisionManager's pid_monitor_thread_count. When this count decreases
// to zero all threads have finished.
auto temp = compiler_pid_set_;
compiler_pid_set_ = nullptr;
delete temp;
// Sleep time between checks starts at 10 ms, but increases to 5 sec after
// 10 sec have passed without the thread count dropping to 0.
unsigned int sleep_time_milliseconds = 10;
unsigned int total_wait_time = 0;
while (pid_monitor_thread_count_ > 0) {
if (sleep_time_milliseconds == 10) {
total_wait_time += sleep_time_milliseconds;
if (total_wait_time >= 10000) {
sleep_time_milliseconds = 5000;
LOGD("Waited %d ms for pid monitor threads to quit, switching sleep"
"time to %d ms", total_wait_time, sleep_time_milliseconds);
}
}
IOSleep(sleep_time_milliseconds);
}
LOGD("Pid monitor threads stopped.");
return true;
}
uint32_t SantaDecisionManager::PidMonitorSleepTimeMilliseconds() const {
return kPidMonitorSleepTimeMilliseconds;
}
#pragma mark Cache Management
void SantaDecisionManager::AddToCache(
@@ -208,6 +303,7 @@ void SantaDecisionManager::AddToCache(
((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_COMPILER:
case ACTION_RESPOND_DENY: {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
@@ -216,6 +312,12 @@ void SantaDecisionManager::AddToCache(
}
break;
}
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
decision_cache_->set(identifier, val, 0);
break;
}
default:
break;
}
@@ -406,6 +508,34 @@ void SantaDecisionManager::DecrementListenerInvocations() {
OSDecrementAtomic(&listener_invocations_);
}
void SantaDecisionManager::IncrementPidMonitorThreadCount() {
OSIncrementAtomic(&pid_monitor_thread_count_);
}
void SantaDecisionManager::DecrementPidMonitorThreadCount() {
OSDecrementAtomic(&pid_monitor_thread_count_);
}
bool SantaDecisionManager::IsCompilerProcess(pid_t pid) {
for (;;) {
// Find the parent pid.
proc_t proc = proc_find(pid);
if (!proc) return false;
pid_t ppid = proc_ppid(proc);
proc_rele(proc);
// Quit if process is launchd or has no parent.
if (ppid == 0 || pid == ppid) break;
pid_t val = compiler_pid_set_->get(pid);
// If pid was in compiler_pid_set_ then make sure that it has the same
// parent pid as when it was set.
if (val) return val == ppid;
// If pid not in the set, then quit unless we want to check ancestors.
if (!kCheckCompilerAncestors) break;
pid = ppid;
}
return false;
}
#pragma mark Callbacks
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
@@ -420,7 +550,9 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
auto returnedAction = FetchDecision(cred, vp, vnode_id);
switch (returnedAction) {
case ACTION_RESPOND_ALLOW: {
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_COMPILER:
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
auto proc = vfs_context_proc(ctx);
if (proc) {
pid_t pid = proc_pid(proc);
@@ -428,6 +560,15 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
uint64_t val = ((uint64_t)pid << 32) | (ppid & 0xFFFFFFFF);
vnode_pid_map_->set(vnode_id, val);
if (returnedAction == ACTION_RESPOND_ALLOW_COMPILER && ppid != 0) {
// Do some additional bookkeeping for compilers:
// We associate the pid with a compiler so that when we see it later
// in the context of a KAUTH_FILEOP event, we'll recognize it.
compiler_pid_set_->set(pid, ppid);
// And start polling for the compiler process termination, so that we
// can remove the pid from our cache of compiler pids.
MonitorCompilerPidForExit(pid);
}
}
return KAUTH_RESULT_ALLOW;
}
@@ -448,28 +589,70 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
void SantaDecisionManager::FileOpCallback(
const kauth_action_t action, const vnode_t vp,
const char *path, const char *new_path) {
if (!ClientConnected() || proc_selfpid() == client_pid_) return;
if (!ClientConnected()) return;
if (vp) {
// KAUTH_FILEOP_CLOSE implies KAUTH_FILEOP_CLOSE_MODIFIED, so remove it from the cache.
if (action == KAUTH_FILEOP_CLOSE) {
auto context = vfs_context_create(nullptr);
RemoveFromCache(GetVnodeIDForVnode(context, vp));
vfs_context_rele(context);
}
// Don't log santad fileops.
if (proc_selfpid() == client_pid_) return;
if (vp && action == KAUTH_FILEOP_EXEC) {
auto context = vfs_context_create(nullptr);
auto vnode_id = GetVnodeIDForVnode(context, vp);
vfs_context_rele(context);
if (action == KAUTH_FILEOP_EXEC) {
auto message = NewMessage(nullptr);
message->vnode_id = vnode_id;
message->action = ACTION_NOTIFY_EXEC;
strlcpy(message->path, path, sizeof(message->path));
uint64_t val = vnode_pid_map_->get(vnode_id);
if (val) {
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
message->pid = (val >> 32);
message->ppid = (val & ~0xFFFFFFFF00000000);
}
PostToLogQueue(message);
delete message;
return;
auto message = NewMessage(nullptr);
message->vnode_id = vnode_id;
message->action = ACTION_NOTIFY_EXEC;
strlcpy(message->path, path, sizeof(message->path));
uint64_t val = vnode_pid_map_->get(vnode_id);
if (val) {
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
message->pid = (val >> 32);
message->ppid = (val & ~0xFFFFFFFF00000000);
}
PostToLogQueue(message);
delete message;
return;
}
// For transitive whitelisting decisions, we must check for KAUTH_FILEOP_CLOSE events from a
// known compiler process. But we must also check for KAUTH_FILEOP_RENAME events because clang
// under Xcode 9 will, if the output file already exists, write to a temp file, delete the
// existing file, then rename the temp file, without ever closing it. So in this scenario,
// the KAUTH_FILEOP_RENAME is the only chance we have of whitelisting the output.
if (action == KAUTH_FILEOP_CLOSE || (action == KAUTH_FILEOP_RENAME && new_path)) {
auto message = NewMessage(nullptr);
if (IsCompilerProcess(message->pid)) {
// Fill out the rest of the message details and send it to the decision queue.
auto context = vfs_context_create(nullptr);
vnode_t real_vp = vp;
// We have to manually look up the vnode pointer from new_path for KAUTH_FILEOP_RENAME.
if (!real_vp && new_path && ERR_SUCCESS == vnode_lookup(new_path, 0, &real_vp, context)) {
vnode_put(real_vp);
}
if (real_vp) message->vnode_id = GetVnodeIDForVnode(context, real_vp);
vfs_context_rele(context);
message->action = ACTION_NOTIFY_WHITELIST;
const char *real_path = (action == KAUTH_FILEOP_CLOSE) ? path : new_path;
strlcpy(message->path, real_path, sizeof(message->path));
proc_name(message->pid, message->pname, sizeof(message->pname));
PostToDecisionQueue(message);
// Add a temporary allow rule to the decision cache for this vnode_id
// while SNTCompilerController decides whether or not to add a
// permanent rule for the new file to the rules database. This is
// because checking if the file is a Mach-O binary and hashing it might
// not finish before an attempt to execute it.
AddToCache(message->vnode_id, ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE, 0);
}
delete message;
// Don't need to do anything else for FILEOP_CLOSE, but FILEOP_RENAME should fall through.
if (action == KAUTH_FILEOP_CLOSE) return;
}
// Filter out modifications to locations that are definitely
@@ -481,7 +664,8 @@ void SantaDecisionManager::FileOpCallback(
proc_name(message->pid, message->pname, sizeof(message->pname));
switch (action) {
case KAUTH_FILEOP_CLOSE:
case KAUTH_FILEOP_WRITE:
// This is actually a KAUTH_VNODE_WRITE_DATA event.
message->action = ACTION_NOTIFY_WRITE;
break;
case KAUTH_FILEOP_RENAME:
@@ -524,6 +708,13 @@ extern "C" int fileop_scope_callback(
char *new_path = nullptr;
switch (action) {
case KAUTH_FILEOP_CLOSE:
// We only care about KAUTH_FILEOP_CLOSE events where the closed file
// was modified.
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED))
return KAUTH_RESULT_DEFER;
// Intentional fallthrough to get vnode reference.
[[fallthrough]];
case KAUTH_FILEOP_DELETE:
case KAUTH_FILEOP_EXEC:
vp = reinterpret_cast<vnode_t>(arg0);
@@ -580,7 +771,9 @@ extern "C" int vnode_scope_callback(
char path[MAXPATHLEN];
int pathlen = MAXPATHLEN;
vn_getpath(vp, path, &pathlen);
sdm->FileOpCallback(KAUTH_FILEOP_CLOSE, vp, path, nullptr);
// KAUTH_VNODE_WRITE_DATA events are translated into fake KAUTH_FILEOP_WRITE
// events so that we can handle them in the FileOpCallback function.
sdm->FileOpCallback(KAUTH_FILEOP_WRITE, vp, path, nullptr);
sdm->DecrementListenerInvocations();
}

View File

@@ -85,6 +85,29 @@ class SantaDecisionManager : public OSObject {
*/
kern_return_t StopListener();
/**
This spins off a new thread for each process that we monitor. Generally the
threads should be short-lived, since they die as soon as their associated
compiler process dies.
*/
void MonitorCompilerPidForExit(pid_t pid);
/// Remove the given pid from cache of compiler pids.
void ForgetCompilerPid(pid_t pid);
/// Returns true when SantaDecisionManager wants monitor threads to exit.
bool PidMonitorThreadsShouldExit() const;
/**
Stops the pid monitor threads. Waits until all threads have stopped before
returning. This also frees the compiler_pid_set_. Returns true if all
threads exited cleanly. Returns false if timed out while waiting.
*/
bool StopPidMonitorThreads();
/// Returns how long pid monitor should sleep between termination checks.
uint32_t PidMonitorSleepTimeMilliseconds() const;
/// Adds a decision to the cache, with a timestamp.
void AddToCache(santa_vnode_id_t identifier,
const santa_action_t decision,
@@ -125,6 +148,19 @@ class SantaDecisionManager : public OSObject {
/// Decrements the count of active callbacks pending.
void DecrementListenerInvocations();
/// Increments the count of active pid monitor threads.
void IncrementPidMonitorThreadCount();
/// Decrements the count of active pid monitor threads.
void DecrementPidMonitorThreadCount();
/**
Determine if pid belongs to a compiler process. When
kCheckCompilerAncestors is set to true, this also checks all ancestor
processes of the pid.
*/
bool IsCompilerProcess(pid_t pid);
/**
Fetches the vnode_id for a given vnode.
@@ -203,6 +239,16 @@ class SantaDecisionManager : public OSObject {
*/
static const uint32_t kMaxLogQueueEvents = 2048;
/// How long pid monitor thread should sleep between termination checks.
static const uint32_t kPidMonitorSleepTimeMilliseconds = 1000;
/**
When set to true, Santa will check all ancestors of a process to determine
if it is a compiler.
TODO(nguyenphillip): this setting (and others above) should be configurable.
*/
static const bool kCheckCompilerAncestors = false;
/**
Fetches a response from the daemon. Handles both daemon death
and failure to post messages to the daemon.
@@ -281,6 +327,7 @@ class SantaDecisionManager : public OSObject {
SantaCache<santa_vnode_id_t, uint64_t> *decision_cache_;
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;
SantaCache<pid_t, pid_t> *compiler_pid_set_;
lck_grp_t *sdm_lock_grp_;
lck_grp_attr_t *sdm_lock_grp_attr_;
@@ -295,6 +342,7 @@ class SantaDecisionManager : public OSObject {
uint32_t failed_log_queue_requests_;
int32_t listener_invocations_;
int32_t pid_monitor_thread_count_ = 0;
pid_t client_pid_;
@@ -308,13 +356,13 @@ class SantaDecisionManager : public OSObject {
/**
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,
@@ -328,13 +376,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

@@ -145,6 +145,19 @@ IOReturn SantaDriverClient::allow_binary(
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::allow_compiler(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
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_COMPILER);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
@@ -181,6 +194,19 @@ IOReturn SantaDriverClient::clear_cache(
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::remove_cache_entry(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
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->RemoveFromCache(*vnode_id);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::cache_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
@@ -234,9 +260,11 @@ IOReturn SantaDriverClient::externalMethod(
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 0, sizeof(santa_vnode_id_t), 0, 0 },
{ &SantaDriverClient::allow_compiler, 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::remove_cache_entry, 0, sizeof(santa_vnode_id_t), 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),

View File

@@ -74,7 +74,7 @@ class com_google_SantaDriverClient : public IOUserClient {
OSObject *target, void *reference) override;
///
/// The userpsace callable methods are below. Each method corresponds
/// The userspace callable methods are below. Each method corresponds
/// to an entry in SantaDriverMethods.
///
@@ -84,7 +84,11 @@ class com_google_SantaDriverClient : public IOUserClient {
/// The daemon calls this to allow a binary.
static IOReturn allow_binary(
OSObject *target, void *reference,IOExternalMethodArguments *arguments);
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a compiler binary.
static IOReturn allow_compiler(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to deny a binary.
static IOReturn deny_binary(
@@ -99,6 +103,10 @@ class com_google_SantaDriverClient : public IOUserClient {
static IOReturn clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon call this to remove a single cache entry.
static IOReturn remove_cache_entry(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in the cache
static IOReturn cache_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);

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

@@ -60,6 +60,12 @@ REGISTER_COMMAND_NAME(@"checkcache")
} else if (action == ACTION_RESPOND_DENY) {
LOGI(@"File exists in [blacklist] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_ALLOW_COMPILER) {
LOGI(@"File exists in [whitelist compiler] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE) {
LOGI(@"File exists in [whitelist pending_transitive] kernel cache");
exit(0);
} else if (action == ACTION_UNSET) {
LOGE(@"File does not exist in cache");
exit(1);

View File

@@ -384,6 +384,12 @@ REGISTER_COMMAND_NAME(@"fileinfo")
case SNTEventStateBlockScope:
[output appendString:@" (Scope)"];
break;
case SNTEventStateAllowCompiler:
[output appendString:@" (Compiler)"];
break;
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
default:
output = @"None".mutableCopy;
break;

View File

@@ -53,6 +53,7 @@ REGISTER_COMMAND_NAME(@"rule")
@" --whitelist: add to whitelist\n"
@" --blacklist: add to blacklist\n"
@" --silent-blacklist: add to silent blacklist\n"
@" --compiler: whitelist and mark as a compiler\n"
@" --remove: remove existing rule\n"
@" --check: check for an existing rule\n"
@"\n"
@@ -65,12 +66,22 @@ REGISTER_COMMAND_NAME(@"rule")
@"\n"
@" Optionally:\n"
@" --certificate: add or check a certificate sha256 rule instead of binary\n"
#ifdef DEBUG
@" --force: allow manual changes even when SyncBaseUrl is set\n"
#endif
@" --message {message}: custom message\n");
}
- (void)runWithArguments:(NSArray *)arguments {
SNTConfigurator *config = [SNTConfigurator configurator];
// DEBUG builds add a --force flag to allow manually adding/removing rules during testing.
#ifdef DEBUG
if ([config syncBaseURL] &&
![arguments containsObject:@"--check"] &&
![arguments containsObject:@"--force"]) {
#else
if ([config syncBaseURL] && ![arguments containsObject:@"--check"]) {
#endif
printf("SyncBaseURL is set, rules are managed centrally.\n");
exit(1);
}
@@ -92,6 +103,8 @@ REGISTER_COMMAND_NAME(@"rule")
newRule.state = SNTRuleStateBlacklist;
} else if ([arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) {
newRule.state = SNTRuleStateSilentBlacklist;
} else if ([arg caseInsensitiveCompare:@"--compiler"] == NSOrderedSame) {
newRule.state = SNTRuleStateWhitelistCompiler;
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
newRule.state = SNTRuleStateRemove;
} else if ([arg caseInsensitiveCompare:@"--check"] == NSOrderedSame) {
@@ -116,6 +129,10 @@ REGISTER_COMMAND_NAME(@"rule")
[self printErrorUsageAndExit:@"--message requires an argument"];
}
newRule.customMsg = arguments[i];
#ifdef DEBUG
} else if ([arg caseInsensitiveCompare:@"--force"] == NSOrderedSame) {
// Don't do anything special.
#endif
} else {
[self printErrorUsageAndExit:[@"Unknown argument: " stringByAppendingString:arg]];
}
@@ -192,6 +209,12 @@ REGISTER_COMMAND_NAME(@"rule")
case SNTEventStateBlockScope:
[output appendString:@" (Scope)"];
break;
case SNTEventStateAllowCompiler:
[output appendString:@" (Compiler)"];
break;
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
default:
output = @"None".mutableCopy;
break;
@@ -214,6 +237,22 @@ REGISTER_COMMAND_NAME(@"rule")
printf("Cannot communicate with daemon");
exit(1);
}
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] databaseRuleForBinarySHA256:fileSHA256
certificateSHA256:certificateSHA256
reply:^(SNTRule *r) {
if (r.state == SNTRuleStateWhitelistTransitive) {
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:r.timestamp];
[output appendString:[NSString stringWithFormat:@"\nlast access date: %@", [date description]]];
}
dispatch_group_leave(group);
}];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
printf("Cannot communicate with daemon");
exit(1);
}
printf("%s\n", output.UTF8String);
exit(0);
}

View File

@@ -96,10 +96,16 @@ REGISTER_COMMAND_NAME(@"status")
// Database counts
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
__block int64_t compilerRuleCount = -1, transitiveRuleCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary,
int64_t certificate,
int64_t compiler,
int64_t transitive) {
binaryRuleCount = binary;
certRuleCount = certificate;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
@@ -148,6 +154,13 @@ REGISTER_COMMAND_NAME(@"status")
}];
}
__block BOOL transitiveWhitelistingEnabled = NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] transitiveWhitelistingEnabled:^(BOOL response) {
transitiveWhitelistingEnabled = response;
dispatch_group_leave(group);
}];
// Wait a maximum of 5s for stats collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
fprintf(stderr, "Failed to retrieve some stats from daemon\n\n");
@@ -179,6 +192,8 @@ REGISTER_COMMAND_NAME(@"status")
@"database" : @{
@"binary_rules" : @(binaryRuleCount),
@"certificate_rules" : @(certRuleCount),
@"compiler_rules" : @(compilerRuleCount),
@"transitive_rules" : @(transitiveRuleCount),
@"events_pending_upload" : @(eventCount),
},
@"sync" : @{
@@ -187,7 +202,8 @@ REGISTER_COMMAND_NAME(@"status")
@"last_successful_full" : fullSyncLastSuccessStr ?: @"null",
@"last_successful_rule" : ruleSyncLastSuccessStr ?: @"null",
@"push_notifications" : pushNotifications ? @"Connected" : @"Disconnected",
@"bundle_scanning" : @(bundlesEnabled)
@"bundle_scanning" : @(bundlesEnabled),
@"transitive_whitelisting" : @(transitiveWhitelistingEnabled),
},
};
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
@@ -207,6 +223,8 @@ REGISTER_COMMAND_NAME(@"status")
printf(">>> Database Info\n");
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %lld\n", "Compiler Rules", compilerRuleCount);
printf(" %-25s | %lld\n", "Transitive Rules", transitiveRuleCount);
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
if (syncURLStr) {
@@ -218,6 +236,8 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %s\n", "Push Notifications",
(pushNotifications ? "Connected" : "Disconnected"));
printf(" %-25s | %s\n", "Bundle Scanning", (bundlesEnabled ? "Yes" : "No"));
printf(" %-25s | %s\n", "Transitive Whitelisting",
(transitiveWhitelistingEnabled ? "Yes" : "No"));
}
}

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

@@ -33,10 +33,13 @@ extern NSString *const kWhitelistRegex;
extern NSString *const kBlacklistRegex;
extern NSString *const kBinaryRuleCount;
extern NSString *const kCertificateRuleCount;
extern NSString *const kCompilerRuleCount;
extern NSString *const kTransitiveRuleCount;
extern NSString *const kFCMToken;
extern NSString *const kFCMFullSyncInterval;
extern NSString *const kFCMGlobalRuleSyncDeadline;
extern NSString *const kBundlesEnabled;
extern NSString *const kTransitiveWhitelistingEnabled;
extern NSString *const kEvents;
extern NSString *const kFileSHA256;
@@ -88,6 +91,7 @@ extern NSString *const kRules;
extern NSString *const kRuleSHA256;
extern NSString *const kRulePolicy;
extern NSString *const kRulePolicyWhitelist;
extern NSString *const kRulePolicyWhitelistCompiler;
extern NSString *const kRulePolicyBlacklist;
extern NSString *const kRulePolicySilentBlacklist;
extern NSString *const kRulePolicyRemove;

View File

@@ -33,10 +33,13 @@ NSString *const kWhitelistRegex = @"whitelist_regex";
NSString *const kBlacklistRegex = @"blacklist_regex";
NSString *const kBinaryRuleCount = @"binary_rule_count";
NSString *const kCertificateRuleCount = @"certificate_rule_count";
NSString *const kCompilerRuleCount = @"compiler_rule_count";
NSString *const kTransitiveRuleCount = @"transitive_rule_count";
NSString *const kFCMToken = @"fcm_token";
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
NSString *const kFCMGlobalRuleSyncDeadline = @"fcm_global_rule_sync_deadline";
NSString *const kBundlesEnabled = @"bundles_enabled";
NSString *const kTransitiveWhitelistingEnabled = @"transitive_whitelisting_enabled";
NSString *const kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -88,6 +91,7 @@ NSString *const kRules = @"rules";
NSString *const kRuleSHA256 = @"sha256";
NSString *const kRulePolicy = @"policy";
NSString *const kRulePolicyWhitelist = @"WHITELIST";
NSString *const kRulePolicyWhitelistCompiler = @"WHITELIST_COMPILER";
NSString *const kRulePolicyBlacklist = @"BLACKLIST";
NSString *const kRulePolicySilentBlacklist = @"SILENT_BLACKLIST";
NSString *const kRulePolicyRemove = @"REMOVE";

View File

@@ -43,9 +43,14 @@
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary,
int64_t certificate,
int64_t compiler,
int64_t transitive) {
requestDict[kBinaryRuleCount] = @(binary);
requestDict[kCertificateRuleCount] = @(certificate);
requestDict[kCompilerRuleCount] = @(compiler);
requestDict[kTransitiveRuleCount] = @(transitive);
dispatch_group_leave(group);
}];
@@ -86,6 +91,14 @@
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
if ([resp[kTransitiveWhitelistingEnabled] respondsToSelector:@selector(boolValue)]) {
BOOL enabled = [resp[kTransitiveWhitelistingEnabled] boolValue];
[[self.daemonConn remoteObjectProxy] setTransitiveWhitelistingEnabled:enabled reply:^{
dispatch_group_leave(group);
}];
}
self.syncState.eventBatchSize = [resp[kBatchSize] unsignedIntegerValue] ?: kDefaultEventBatchSize;
self.syncState.FCMToken = resp[kFCMToken];

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];
@@ -132,6 +136,8 @@
NSString *policyString = dict[kRulePolicy];
if ([policyString isEqual:kRulePolicyWhitelist]) {
newRule.state = SNTRuleStateWhitelist;
} else if ([policyString isEqual:kRulePolicyWhitelistCompiler]) {
newRule.state = SNTRuleStateWhitelistCompiler;
} else if ([policyString isEqual:kRulePolicyBlacklist]) {
newRule.state = SNTRuleStateBlacklist;
} else if ([policyString isEqual:kRulePolicySilentBlacklist]) {
@@ -157,7 +163,7 @@
}
// Check rule for extra notification related info.
if (newRule.state == SNTRuleStateWhitelist) {
if (newRule.state == SNTRuleStateWhitelist || newRule.state == SNTRuleStateWhitelistCompiler) {
// primaryHash is the bundle hash if there was a bundle hash included in the rule, otherwise
// it is simply the binary hash.
NSString *primaryHash = dict[kFileBundleHash];

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

@@ -17,6 +17,7 @@
#import "SNTCommonEnums.h"
#import "SNTDatabaseTable.h"
@class SNTCachedDecision;
@class SNTRule;
@class SNTNotificationMessage;
@@ -35,6 +36,16 @@
///
- (NSUInteger)binaryRuleCount;
///
/// @return Number of compiler rules in the database
///
- (NSUInteger)compilerRuleCount;
///
/// @return Number of transitive rules in the database
///
- (NSUInteger)transitiveRuleCount;
///
/// @return Number of certificate rules in the database
///
@@ -58,4 +69,32 @@
///
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate error:(NSError **)error;
///
/// Checks the given array of rules to see if adding any of them to the rules database would
/// require the kernel's decision cache to be flushed. This should happen if
/// 1. any of the rules is not a SNTRuleStateWhitelist
/// 2. a SNTRuleStateWhitelist rule is replacing a SNTRuleStateWhitelistCompiler rule.
///
/// @param rules Array of SNTRule that may be added to database.
/// @return YES if kernel cache should be flushed after adding the new rules.
- (BOOL)addedRulesShouldFlushDecisionCache:(NSArray *)rules;
///
/// Update timestamp for given rule to the current time.
///
- (void)resetTimestampForRule:(SNTRule *)rule;
///
/// Remove transitive rules that haven't been used in a long time.
///
- (void)removeOutdatedTransitiveRules;
///
/// A map of a file hashes to cached decisions. This is used to pre-validate and whitelist
/// certain critical system binaries that are integral to Santa's functionality.
///
@property(readonly, nonatomic)
NSDictionary<NSString *, SNTCachedDecision *> *criticalSystemBinaries;
@end

View File

@@ -17,17 +17,32 @@
#import <MOLCertificate/MOLCertificate.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import "SNTCachedDecision.h"
#import "SNTConfigurator.h"
#import "SNTFileInfo.h"
#import "SNTLogging.h"
#import "SNTRule.h"
// TODO(nguyenphillip): this should be configurable.
// How many rules must be in database before we start trying to remove transitive rules.
static const NSUInteger kTransitiveRuleCullingThreshold = 500000;
// Consider transitive rules out of date if they haven't been used in six months.
static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
@interface SNTRuleTable ()
@property NSString *santadCertSHA;
@property NSString *launchdCertSHA;
@property NSDate *lastTransitiveRuleCulling;
@property NSDictionary *criticalSystemBinaries;
@property(readonly) NSArray *criticalSystemBinaryPaths;
@end
@implementation SNTRuleTable
- (NSArray *)criticalSystemBinaryPaths {
return @[ @"/usr/libexec/trustd", @"/usr/sbin/securityd", @"/usr/libexec/xpcproxy" ];
}
- (uint32_t)initializeDatabase:(FMDatabase *)db fromVersion:(uint32_t)version {
// Lock this database from other processes
[[db executeQuery:@"PRAGMA locking_mode = EXCLUSIVE;"] close];
@@ -57,7 +72,8 @@
// Save hashes of the signing certs for launchd and santad.
// Used to ensure rules for them are not removed.
self.santadCertSHA = [[[[MOLCodesignChecker alloc] initWithSelf] leafCertificate] SHA256];
self.launchdCertSHA = [[[[MOLCodesignChecker alloc] initWithPID:1] leafCertificate] SHA256];
MOLCodesignChecker *launchdCSInfo = [[MOLCodesignChecker alloc] initWithPID:1];
self.launchdCertSHA = launchdCSInfo.leafCertificate.SHA256;
// Ensure the certificates used to sign the running launchd/santad are whitelisted.
// If they weren't previously and the database is not new, log an error.
@@ -65,6 +81,13 @@
@"FROM rules "
@"WHERE (shasum=? OR shasum=?) AND state=? AND type=2",
self.santadCertSHA, self.launchdCertSHA, @(SNTRuleStateWhitelist)];
if (version < 3) {
// Add timestamp column for tracking age of transitive rules.
[db executeUpdate:@"ALTER TABLE 'rules' ADD 'timestamp' INTEGER"];
newVersion = 3;
}
if (ruleCount != 2) {
if (version > 0) LOGE(@"Started without launchd/santad certificate rules in place!");
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
@@ -73,6 +96,34 @@
self.launchdCertSHA, @(SNTRuleStateWhitelist), @(SNTRuleTypeCertificate)];
}
// Setup critical system binaries
// TODO(tburgin): Add the Santa components to this feature and remove the santadCertSHA rule.
NSMutableDictionary *bins = [NSMutableDictionary dictionary];
for (NSString *path in self.criticalSystemBinaryPaths) {
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:path];
MOLCodesignChecker *csInfo = [binInfo codesignCheckerWithError:NULL];
// Make sure the critical system binary is signed by the same chain as launchd.
if ([csInfo signingInformationMatches:launchdCSInfo]) {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.decision = SNTEventStateAllowBinary;
cd.decisionExtra = @"critical system binary";
cd.sha256 = binInfo.SHA256;
// Not needed, but nice for logging.
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
bins[binInfo.SHA256] = cd;
} else {
LOGE(@"Unable to validate critical system binary. pid 1: %@ and %@: %@ do not match.",
launchdCSInfo.leafCertificate, path, csInfo.leafCertificate);
}
}
self.criticalSystemBinaries = bins;
return newVersion;
}
@@ -102,15 +153,30 @@
return count;
}
- (NSUInteger)compilerRuleCount {
__block NSUInteger count = 0;
[self inDatabase:^(FMDatabase *db) {
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE state=?",
@(SNTRuleStateWhitelistCompiler)];
}];
return count;
}
- (NSUInteger)transitiveRuleCount {
__block NSUInteger count = 0;
[self inDatabase:^(FMDatabase *db) {
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE state=?",
@(SNTRuleStateWhitelistTransitive)];
}];
return count;
}
- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
SNTRule *rule = [[SNTRule alloc] init];
rule.shasum = [rs stringForColumn:@"shasum"];
rule.type = [rs intForColumn:@"type"];
rule.state = [rs intForColumn:@"state"];
rule.customMsg = [rs stringForColumn:@"custommsg"];
return rule;
return [[SNTRule alloc] initWithShasum:[rs stringForColumn:@"shasum"]
state:[rs intForColumn:@"state"]
type:[rs intForColumn:@"type"]
customMsg:[rs stringForColumn:@"custommsg"]
timestamp:[rs intForColumn:@"timestamp"]];
}
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
@@ -165,7 +231,7 @@
for (SNTRule *rule in rules) {
if (![rule isKindOfClass:[SNTRule class]] || rule.shasum.length == 0 ||
rule.state == SNTRuleStateUnknown || rule.type == SNTRuleTypeUnknown) {
[self fillError:error code:SNTRuleTableErrorInvalidRule message:nil];
[self fillError:error code:SNTRuleTableErrorInvalidRule message:rule.description];
*rollback = failed = YES;
return;
}
@@ -181,9 +247,10 @@
}
} else {
if (![db executeUpdate:@"INSERT OR REPLACE INTO rules "
@"(shasum, state, type, custommsg) "
@"VALUES (?, ?, ?, ?);",
rule.shasum, @(rule.state), @(rule.type), rule.customMsg]) {
@"(shasum, state, type, custommsg, timestamp) "
@"VALUES (?, ?, ?, ?, ?);",
rule.shasum, @(rule.state), @(rule.type), rule.customMsg,
@(rule.timestamp)]) {
[self fillError:error
code:SNTRuleTableErrorInsertOrReplaceFailed
message:[db lastErrorMessage]];
@@ -197,6 +264,67 @@
return !failed;
}
- (BOOL)addedRulesShouldFlushDecisionCache:(NSArray *)rules {
// Check for non-plain-whitelist rules first before querying the database.
for (SNTRule *rule in rules) {
if (rule.state != SNTRuleStateWhitelist) return YES;
}
// If still here, then all rules in the array are whitelist rules. So now we look for whitelist
// rules where there is a previously existing whitelist compiler rule for the same shasum.
// If so we find such a rule, then cache should be flushed.
__block BOOL flushDecisionCache = NO;
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
for (SNTRule *rule in rules) {
// Whitelist certificate rules are ignored
if (rule.type == SNTRuleTypeCertificate) continue;
if ([db longForQuery:
@"SELECT COUNT(*) FROM rules WHERE shasum=? AND type=? AND state=? LIMIT 1",
rule.shasum, @(SNTRuleTypeBinary), @(SNTRuleStateWhitelistCompiler)] > 0) {
flushDecisionCache = YES;
break;
}
}
}];
return flushDecisionCache;
}
// Updates the timestamp to current time for the given rule.
- (void)resetTimestampForRule:(SNTRule *)rule {
if (!rule) return;
[rule resetTimestamp];
[self inDatabase:^(FMDatabase *db) {
if (![db executeUpdate:@"UPDATE rules SET timestamp=? WHERE shasum=? AND type=?",
@(rule.timestamp), rule.shasum, @(rule.type)]) {
LOGE(@"Could not update timestamp for rule with sha256=%@", rule.shasum);
}
}];
}
- (void)removeOutdatedTransitiveRules {
// Don't attempt to remove transitive rules unless it's been at least an hour since the
// last time we tried to remove them.
if (self.lastTransitiveRuleCulling &&
-[self.lastTransitiveRuleCulling timeIntervalSinceNow] < 3600) return;
// Don't bother removing rules unless rule database is large.
if ([self ruleCount] < kTransitiveRuleCullingThreshold) return;
// Determine what timestamp qualifies as outdated.
NSUInteger outdatedTimestamp =
[[NSDate date] timeIntervalSinceReferenceDate] - kTransitiveRuleExpirationSeconds;
[self inDatabase:^(FMDatabase *db) {
if (![db executeUpdate:@"DELETE FROM rules WHERE state=? AND timestamp < ?",
@(SNTRuleStateWhitelistTransitive), @(outdatedTimestamp)]) {
LOGE(@"Could not remove outdated transitive rules");
}
}];
self.lastTransitiveRuleCulling = [NSDate date];
}
// Helper to create an NSError where necessary.
// The return value is irrelevant but the static analyzer complains if it's not a BOOL.
- (BOOL)fillError:(NSError **)error code:(SNTRuleTableError)code message:(NSString *)message {
@@ -208,7 +336,8 @@
d[NSLocalizedDescriptionKey] = @"Empty rule array";
break;
case SNTRuleTableErrorInvalidRule:
d[NSLocalizedDescriptionKey] = @"Rule array contained invalid entry";
d[NSLocalizedDescriptionKey] =
[NSString stringWithFormat:@"Rule array contained invalid entry: %@", message];
break;
case SNTRuleTableErrorInsertOrReplaceFailed:
d[NSLocalizedDescriptionKey] = @"A database error occurred while inserting/replacing a rule";

View File

@@ -30,10 +30,15 @@
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message;
- (void)logAllowedExecution:(santa_message_t)message;
- (void)logBundleHashingEvents:(NSArray<SNTStoredEvent *> *)events;
- (void)writeLog:(NSString *)log;
// Getter and setter for cached decisions.
- (SNTCachedDecision *)cachedDecisionForMessage:(santa_message_t)message;
// Methods for storing, retrieving, and removing cached decisions.
- (void)cacheDecision:(SNTCachedDecision *)cd;
- (SNTCachedDecision *)cachedDecisionForMessage:(santa_message_t)message;
- (void)forgetCachedDecisionForVnodeId:(santa_vnode_id_t)vnodeId;
// Method used to record the freshness of transitive rules.
- (void)resetTimestampForCachedDecision:(SNTCachedDecision *)cd;
// String formatter helpers.
- (void)addArgsForPid:(pid_t)pid toString:(NSMutableString *)str;

View File

@@ -21,10 +21,15 @@
#import "SNTCachedDecision.h"
#import "SNTConfigurator.h"
#import "SNTDatabaseController.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
@interface SNTEventLog ()
@property NSMutableDictionary<NSNumber *, SNTCachedDecision *> *detailStore;
@property dispatch_queue_t detailStoreQueue;
// Cache for sha256 -> date of last timestamp reset.
@property NSCache<NSString *, NSDate *> *timestampResetMap;
@end
@implementation SNTEventLog
@@ -40,6 +45,8 @@
_userNameMap.countLimit = 100;
_groupNameMap = [[NSCache alloc] init];
_groupNameMap.countLimit = 100;
_timestampResetMap = [[NSCache alloc] init];
_timestampResetMap.countLimit = 100;
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
@@ -93,6 +100,28 @@
return cd;
}
- (void)forgetCachedDecisionForVnodeId:(santa_vnode_id_t)vnodeId {
dispatch_sync(self.detailStoreQueue, ^{
[self.detailStore removeObjectForKey:@(vnodeId.fileid)];
});
}
// Whenever a cached decision resulting from a transitive whitelist rule is used to allow the
// execution of a binary, we update the timestamp on the transitive rule in the rules database.
// To prevent writing to the database too often, we space out consecutive writes by 3600 seconds.
- (void)resetTimestampForCachedDecision:(SNTCachedDecision *)cd {
if (cd.decision != SNTEventStateAllowTransitive) return;
NSDate *lastUpdate = [self.timestampResetMap objectForKey:cd.sha256];
if (!lastUpdate || -[lastUpdate timeIntervalSinceNow] > 3600) {
SNTRule *rule = [[SNTRule alloc] initWithShasum:cd.sha256
state:SNTRuleStateWhitelistTransitive
type:SNTRuleTypeBinary
customMsg:nil];
[[SNTDatabaseController ruleTable] resetTimestampForRule:rule];
[self.timestampResetMap setObject:[NSDate date] forKey:cd.sha256];
}
}
/**
Sanitizes a given string if necessary, otherwise returns the original.
*/

View File

@@ -86,6 +86,21 @@
r = @"BINARY";
logArgs = YES;
break;
case SNTEventStateAllowCompiler:
d = @"ALLOW";
r = @"COMPILER";
logArgs = YES;
break;
case SNTEventStateAllowTransitive:
d = @"ALLOW";
r = @"TRANSITIVE";
logArgs = YES;
break;
case SNTEventStateAllowPendingTransitive:
d = @"ALLOW";
r = @"PENDING_TRANSITIVE";
logArgs = YES;
break;
case SNTEventStateAllowCertificate:
d = @"ALLOW";
r = @"CERT";
@@ -183,6 +198,10 @@
- (void)logAllowedExecution:(santa_message_t)message {
SNTCachedDecision *cd = [self cachedDecisionForMessage:message];
[self logExecution:message withDecision:cd];
// We also reset the timestamp for transitive rules here, because it happens to be where we
// have access to both the execution notification and the sha256 associated with rule.
[self resetTimestampForCachedDecision:cd];
}
- (void)logDiskAppeared:(NSDictionary *)diskProperties {

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

@@ -19,6 +19,7 @@
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "SNTCommonEnums.h"
#import "SNTCompilerController.h"
#import "SNTConfigurator.h"
#import "SNTDaemonControlController.h"
#import "SNTDatabaseController.h"
@@ -33,6 +34,7 @@
#import "SNTSyncdQueue.h"
#import "SNTSyslogEventLog.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCUnprivilegedControlInterface.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTApplication ()
@@ -40,6 +42,7 @@
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@property SNTExecutionController *execController;
@property SNTCompilerController *compilerController;
@property MOLXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@@ -117,10 +120,15 @@
_controlConnection =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]];
_controlConnection.exportedInterface = [SNTXPCControlInterface controlInterface];
_controlConnection.privilegedInterface = [SNTXPCControlInterface controlInterface];
_controlConnection.unprivilegedInterface = [SNTXPCUnprivilegedControlInterface controlInterface];
_controlConnection.exportedObject = dc;
[_controlConnection resume];
// Initialize the transitive whitelisting controller object.
_compilerController = [[SNTCompilerController alloc] initWithDriverManager:_driverManager
eventLog:_eventLog];
// Initialize the binary checker object
_execController = [[SNTExecutionController alloc] initWithDriverManager:_driverManager
ruleTable:ruleTable
@@ -163,6 +171,12 @@
[_execController validateBinaryWithMessage:message];
break;
}
case ACTION_NOTIFY_WHITELIST: {
// Determine if we should add a transitive whitelisting rule for this new file.
// Requires that writing process was a compiler and that new file is executable.
[self.compilerController createTransitiveRule:message];
break;
}
default: {
LOGE(@"Received decision request without a valid action: %d", message.action);
exit(1);
@@ -261,10 +275,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);

View File

@@ -0,0 +1,31 @@
/// Copyright 2017 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
#import "SNTKernelCommon.h"
@class SNTDriverManager;
@class SNTEventLog;
@interface SNTCompilerController : NSObject
// Designated initializer takes a SNTEventLog instance so that we can
// call saveDecisionDetails: to create a fake cached decision for transitive
// rule creation requests that are still pending.
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
eventLog:(SNTEventLog *)eventLog;
// Whenever an executable file is closed or renamed whitelist the resulting file.
// We assume that we have already determined that the writing process was a compiler.
- (void)createTransitiveRule:(santa_message_t)message;
@end

View File

@@ -0,0 +1,100 @@
/// Copyright 2017 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCompilerController.h"
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTEventLog.h"
#import "SNTFileInfo.h"
#import "SNTKernelCommon.h"
#import "SNTLogging.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
@interface SNTCompilerController ()
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@end
@implementation SNTCompilerController
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
eventLog:(SNTEventLog *)eventLog {
self = [super init];
if (self) {
_driverManager = driverManager;
_eventLog = eventLog;
}
return self;
}
// Adds a fake cached decision to SNTEventLog for pending files. If the file
// is executed before we can create a transitive rule for it, then we can at
// least log the pending decision info.
- (void)saveFakeDecision:(santa_message_t)message {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.decision = SNTEventStateAllowPendingTransitive;
cd.vnodeId = message.vnode_id;
cd.sha256 = @"pending";
[self.eventLog cacheDecision:cd];
}
- (void)removeFakeDecision:(santa_message_t)message {
[self.eventLog forgetCachedDecisionForVnodeId:message.vnode_id];
}
// Assume that this method is called only when we already know that the writing process is a
// compiler. It checks if the closed file is executable, and if so, transitively whitelists it.
// The passed in message contains the pid of the writing process and path of closed file.
- (void)createTransitiveRule:(santa_message_t)message {
[self saveFakeDecision:message];
char *target = message.path;
// Check if this file is an executable.
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:@(target)];
if (fi.isExecutable) {
// Check if there is an existing (non-transitive) rule for this file. We leave existing rules
// alone, so that a whitelist or blacklist rule can't be overwritten by a transitive one.
SNTRuleTable *ruleTable = [SNTDatabaseController ruleTable];
SNTRule *prevRule = [ruleTable ruleForBinarySHA256:fi.SHA256 certificateSHA256:nil];
if (!prevRule || prevRule.state == SNTRuleStateWhitelistTransitive) {
// Construct a new transitive whitelist rule for the executable.
SNTRule *rule = [[SNTRule alloc] initWithShasum:fi.SHA256
state:SNTRuleStateWhitelistTransitive
type:SNTRuleTypeBinary
customMsg:@""];
// Add the new rule to the rules database.
NSError *err;
if (![ruleTable addRules:@[ rule ] cleanSlate:NO error:&err]) {
LOGE(@"unable to add new transitive rule to database: %@", err.localizedDescription);
} else {
[self.eventLog
writeLog:[NSString stringWithFormat:@"action=WHITELIST|pid=%d|path=%s|sha256=%@",
message.pid, target, fi.SHA256]];
}
}
}
// Remove the temporary allow rule in the kernel decision cache.
[self.driverManager removeCacheEntryForVnodeID:message.vnode_id];
// Remove the "pending" decision info from SNTEventLog.
[self removeFakeDecision:message];
}
@end

View File

@@ -93,21 +93,34 @@ double watchdogRAMPeak = 0;
#pragma mark Database ops
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply {
- (void)databaseRuleCounts:(void (^)(int64_t binary,
int64_t certificate,
int64_t compiler,
int64_t transitive))reply {
SNTRuleTable *rdb = [SNTDatabaseController ruleTable];
reply([rdb binaryRuleCount], [rdb certificateRuleCount]);
reply([rdb binaryRuleCount], [rdb certificateRuleCount],
[rdb compilerRuleCount], [rdb transitiveRuleCount]);
}
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(NSError *error))reply {
NSError *error;
[[SNTDatabaseController ruleTable] addRules:rules cleanSlate:cleanSlate error:&error];
SNTRuleTable *ruleTable = [SNTDatabaseController ruleTable];
// If any rules were added that were not whitelist, flush cache.
NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF.state != %d", SNTRuleStateWhitelist];
if ([rules filteredArrayUsingPredicate:p].count || cleanSlate) {
LOGI(@"Received non-whitelist rule, flushing cache");
// If any rules are added that are not plain whitelist rules, then flush decision cache.
// In particular, the addition of whitelist compiler rules should cause a cache flush.
// We also flush cache if a whitelist compiler rule is replaced with a whitelist rule.
BOOL flushCache = (cleanSlate || [ruleTable addedRulesShouldFlushDecisionCache:rules]);
NSError *error;
[ruleTable addRules:rules cleanSlate:cleanSlate error:&error];
// Whenever we add rules, we can also check for and remove outdated transitive rules.
[ruleTable removeOutdatedTransitiveRules];
// The actual cache flushing happens after the new rules have been added to the database.
if (flushCache) {
LOGI(@"Flushing decision cache");
[self.driverManager flushCache];
}
@@ -220,6 +233,15 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)transitiveWhitelistingEnabled:(void (^)(BOOL))reply {
reply([SNTConfigurator configurator].transitiveWhitelistingEnabled);
}
- (void)setTransitiveWhitelistingEnabled:(BOOL)enabled reply:(void (^)(void))reply {
[[SNTConfigurator configurator] setTransitiveWhitelistingEnabled:enabled];
reply();
}
#pragma mark GUI Ops
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener {

View File

@@ -64,11 +64,16 @@
- (BOOL)flushCache;
///
/// Check the kernel cache for a VnodeID
/// Check the kernel cache for a VnodeID.
///
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID;
/// Returns whether the connection to the driver has been established.
@property(readonly) BOOL connectionEstablished;
///
/// Remove single entry from the kernel cache for given VnodeID.
///
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId;
@end

View File

@@ -201,6 +201,13 @@ static void driverAppearedHandler(void *info, io_iterator_t iterator) {
sizeof(vnodeId),
0,
0);
case ACTION_RESPOND_ALLOW_COMPILER:
return IOConnectCallStructMethod(_connection,
kSantaUserClientAllowCompiler,
&vnodeId,
sizeof(vnodeId),
0,
0);
default:
return KERN_INVALID_ARGUMENT;
}
@@ -229,6 +236,15 @@ static void driverAppearedHandler(void *info, io_iterator_t iterator) {
0) == KERN_SUCCESS;
}
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId {
return IOConnectCallStructMethod(_connection,
kSantaUserClientRemoveCacheEntry,
&vnodeId,
sizeof(vnodeId),
0,
0);
}
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
uint64_t output;
uint32_t outputCnt = 1;

View File

@@ -115,36 +115,54 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
[_driverManager postToKernelAction:ACTION_RESPOND_ACK forVnodeID:message.vnode_id];
}
// 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;
// If the binary is a critical system binary, don't check its signature. The binary was validated
// by santad at startup.
SNTCachedDecision *cd = self.ruleTable.criticalSystemBinaries[binInfo.SHA256];
MOLCodesignChecker *csInfo; // Needed further down in this scope.
if (!cd) {
// Get codesigning info about the file but only if it's a Mach-O.
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 (and refresh rule access timestamp).
cd = [self.policyProcessor decisionForFileInfo:binInfo
fileSHA256:nil
certificateSHA256:csInfo.leafCertificate.SHA256];
cd.certCommonName = csInfo.leafCertificate.commonName;
}
// Actually make the decision.
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo
fileSHA256:nil
certificateSHA256:csInfo.leafCertificate.SHA256];
cd.certCommonName = csInfo.leafCertificate.commonName;
cd.vnodeId = message.vnode_id;
// Formulate an action from the decision
// Formulate an initial action from the decision.
santa_action_t action =
(SNTEventStateAllow & cd.decision) ? ACTION_RESPOND_ALLOW : ACTION_RESPOND_DENY;
// Save decision details for logging the execution later.
if (action == ACTION_RESPOND_ALLOW) [_eventLog cacheDecision:cd];
// Upgrade the action to ACTION_RESPOND_ALLOW_COMPILER when appropriate, because we want the
// kernel to track this information in its decision cache.
if (cd.decision == SNTEventStateAllowCompiler) {
action = ACTION_RESPOND_ALLOW_COMPILER;
}
// Save decision details for logging the execution later. For transitive rules, we also use
// the shasum stored in the decision details to update the rule's timestamp whenever an
// ACTION_NOTIFY_EXEC message related to the transitive rule is received.
if (action == ACTION_RESPOND_ALLOW || action == ACTION_RESPOND_ALLOW_COMPILER) {
[_eventLog cacheDecision:cd];
}
// Send the decision to the kernel.
[_driverManager postToKernelAction:action forVnodeID:message.vnode_id];
// Log to database if necessary.
if (cd.decision != SNTEventStateAllowBinary &&
cd.decision != SNTEventStateAllowCompiler &&
cd.decision != SNTEventStateAllowTransitive &&
cd.decision != SNTEventStateAllowCertificate &&
cd.decision != SNTEventStateAllowScope) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
@@ -188,7 +206,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
});
// If binary was blocked, do the needful
if (action != ACTION_RESPOND_ALLOW) {
if (action != ACTION_RESPOND_ALLOW && action != ACTION_RESPOND_ALLOW_COMPILER) {
[_eventLog logDeniedExecution:cd withMessage:message];
if ([[SNTConfigurator configurator] bundlesEnabled] && binInfo.bundle) {

View File

@@ -39,6 +39,7 @@
- (SNTCachedDecision *)decisionForFileInfo:(SNTFileInfo *)fileInfo
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256 {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
cd.certSHA256 = certificateSHA256;
@@ -59,6 +60,26 @@
cd.customMsg = rule.customMsg;
cd.decision = SNTEventStateBlockBinary;
return cd;
case SNTRuleStateWhitelistCompiler:
// If transitive whitelisting is enabled, then SNTRuleStateWhiteListCompiler rules
// become SNTEventStateAllowCompiler decisions. Otherwise we treat the rule as if
// it were SNTRuleStateWhitelist.
if ([[SNTConfigurator configurator] transitiveWhitelistingEnabled]) {
cd.decision = SNTEventStateAllowCompiler;
} else {
cd.decision = SNTEventStateAllow;
}
return cd;
case SNTRuleStateWhitelistTransitive:
// If transitive whitelisting is enabled, then SNTRuleStateWhitelistTransitive
// rules become SNTEventStateAllowTransitive decisions. Otherwise, we treat the
// rule as if it were SNTRuleStateUnknown.
if ([[SNTConfigurator configurator] transitiveWhitelistingEnabled]) {
cd.decision = SNTEventStateAllowTransitive;
return cd;
} else {
rule.state = SNTRuleStateUnknown;
}
default: break;
}
break;

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

@@ -99,20 +99,53 @@
return @(buf);
}
/// Return the path to the version of ld being used by clang.
- (NSString *)ldPath {
static NSString *path;
if (!path) {
NSTask *xcrun = [self taskWithPath:@"/usr/bin/xcrun"];
xcrun.arguments = @[@"-f", @"ld"];
xcrun.standardOutput = [NSPipe pipe];
@try {
[xcrun launch];
[xcrun waitUntilExit];
} @catch (NSException *exception) {
return nil;
}
if (xcrun.terminationStatus != 0) return nil;
NSData *data = [[xcrun.standardOutput fileHandleForReading] readDataToEndOfFile];
if (!data) return nil;
path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
return path;
}
#pragma mark - Driver Helpers
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
/// passing the |vnodeID|.
- (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) {
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
} else {
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
switch (action) {
case ACTION_RESPOND_ALLOW:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_DENY:
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ACK:
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, sizeof(vnodeid), 0, 0);
break;
case ACTION_RESPOND_ALLOW_COMPILER:
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler,
&vnodeid, sizeof(vnodeid), 0, 0);
break;
default:
TFAILINFO("postToKernelAction:forVnodeID: received unknown action type: %d", action);
break;
}
}
@@ -547,6 +580,127 @@
TPASS();
}
- (void)testPendingTransitiveRules {
TSTART("Adds pending transitive whitelist rules");
NSString *ldPath = [self ldPath];
if (!ldPath) {
TFAILINFO("Couldn't get path to ld");
}
// Clear out cached decisions from any previous tests.
[self flushCache];
__block int ldCount = 0;
__block int helloCount = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (!strcmp(ldPath.UTF8String, msg.path)) {
ldCount++;
return ACTION_RESPOND_ALLOW_COMPILER;
} else if (!strcmp("/private/tmp/hello", msg.path)) {
helloCount++;
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Write source file to /tmp/hello.c
FILE *out = fopen("/tmp/hello.c", "wb");
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
fclose(out);
// Then compile it with clang and ld, the latter of which has been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[@"-o", @"/private/tmp/hello", @"/private/tmp/hello.c"];
[clang launch];
[clang waitUntilExit];
// Make sure that our version of ld marked as compiler was run. This assumes that
// "xcode-select -p" returns "/Applications/Xcode.app/Contents/Developer"
if (ldCount != 1) {
TFAILINFO("Didn't record run of ld");
}
// Check if we can now run /private/tmp/hello. If working correctly, there will already be
// a pending transitive rule in the cache, so no decision request will be sent to listener.
// If for some reason a decision request is sent, then binary will be denied.
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
@try {
[hello launch];
[hello waitUntilExit];
} @catch (NSException *exception) {
TFAILINFO("could not launch /private/tmp/hello: %s", exception.reason.UTF8String);
}
// Check that the listener was not consulted for the decision.
if (helloCount > 0) {
TFAILINFO("pending decision for /private/tmp/hello was not in cache");
}
// Clean up
remove("/tmp/hello");
remove("/tmp/hello.c");
TPASS();
}
- (void)testNoTransitiveRules {
TSTART("No transitive rule generated by non-compiler");
NSString *ldPath = [self ldPath];
if (!ldPath) {
TFAILINFO("Couldn't get path to ld");
}
// Clear out cached decisions from any previous tests.
[self flushCache];
__block int ldCount = 0;
__block int helloCount = 0;
self.handlerBlock = ^santa_action_t(santa_message_t msg) {
if (!strcmp(ldPath.UTF8String, msg.path)) {
ldCount++;
return ACTION_RESPOND_ALLOW;
} else if (!strcmp("/private/tmp/hello", msg.path)) {
helloCount++;
return ACTION_RESPOND_DENY;
}
return ACTION_RESPOND_ALLOW;
};
// Write source file to /tmp/hello.c
FILE *out = fopen("/tmp/hello.c", "wb");
fprintf(out, "#include <stdio.h>\nint main(void) { printf(\"Hello, world!\\n\"); }");
fclose(out);
// Then compile it with clang and ld, neither of which have been marked as a compiler.
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
clang.arguments = @[@"-o", @"/private/tmp/hello", @"/private/tmp/hello.c"];
@try {
[clang launch];
[clang waitUntilExit];
} @catch (NSException *exception) {
TFAILINFO("Couldn't launch clang");
}
// Make sure that our version of ld was run. This assumes that "xcode-select -p"
// returns "/Applications/Xcode.app/Contents/Developer"
if (ldCount != 1) {
TFAILINFO("Didn't record run of ld");
}
// Check that we cannot run /private/tmp/hello.
NSTask *hello = [self taskWithPath:@"/private/tmp/hello"];
@try {
[hello launch];
[hello waitUntilExit];
TFAILINFO("Should not have been able to launch /private/tmp/hello");
} @catch (NSException *exception) {
// All good
}
// Check that there wasn't a decision for /private/tmp/hello in the cache.
if (helloCount != 1) {
TFAILINFO("decision for /private/tmp/hello found in cache");
}
// Clean up
remove("/tmp/hello");
remove("/tmp/hello.c");
TPASS();
}
#pragma mark - Main
- (void)unloadDaemon {
@@ -620,6 +774,8 @@
[self clearCacheTests];
[self blocksDeniedTracedBinaries];
[self testLargeBinary];
[self testPendingTransitiveRules];
[self testNoTransitiveRules];
printf("\n-> Performance tests:\n");
[self testCachePerformance];

View File

@@ -191,15 +191,20 @@
- (void)testPreflightDatabaseCounts {
SNTCommandSyncPreflight *sut = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
int64_t bin = 5, cert = 8;
OCMStub([self.daemonConnRop databaseRuleCounts:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(bin),
OCMOCK_VALUE(cert),
nil])]);
int64_t bin = 5, cert = 8, compiler = 2, transitive = 19;
OCMStub([self.daemonConnRop databaseRuleCounts:([OCMArg invokeBlockWithArgs:
OCMOCK_VALUE(bin),
OCMOCK_VALUE(cert),
OCMOCK_VALUE(compiler),
OCMOCK_VALUE(transitive),
nil])]);
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
NSDictionary *requestDict = [self dictFromRequest:req];
XCTAssertEqualObjects(requestDict[kBinaryRuleCount], @(5));
XCTAssertEqualObjects(requestDict[kCertificateRuleCount], @(8));
XCTAssertEqualObjects(requestDict[kCompilerRuleCount], @(2));
XCTAssertEqualObjects(requestDict[kTransitiveRuleCount], @(19));
return YES;
}];

View File

@@ -165,6 +165,71 @@
forVnodeID:[self getVnodeId]]);
}
- (void)testBinaryWhitelistCompilerRule {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
OCMStub([self.mockConfigurator transitiveWhitelistingEnabled]).andReturn(YES);
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateWhitelistCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW_COMPILER
forVnodeID:[self getVnodeId]]);
}
- (void)testBinaryWhitelistCompilerRuleDisabled {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
OCMStub([self.mockConfigurator transitiveWhitelistingEnabled]).andReturn(NO);
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateWhitelistCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:[self getVnodeId]]);
}
- (void)testBinaryWhitelistTransitiveRule {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
OCMStub([self.mockConfigurator transitiveWhitelistingEnabled]).andReturn(YES);
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateWhitelistTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:[self getVnodeId]]);
}
- (void)testBinaryWhitelistTransitiveRuleDisabled {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
OCMStub([self.mockConfigurator transitiveWhitelistingEnabled]).andReturn(NO);
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateWhitelistTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_DENY
forVnodeID:[self getVnodeId]]);
}
- (void)testDefaultDecision {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");

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