Compare commits

...

41 Commits
1.15 ... 2021.5

Author SHA1 Message Date
Russell Hancox
1d9af01353 Project: Bump dependency versions, fix reload command (#554)
* Project: Bump dependency versions, fix reload command

The reload command would fail if you used multiple compilation modes for
building as it would try extracting the versions from both comp modes.

The dependency bump includes a fix for #553
2021-05-03 21:37:42 -04:00
Adam Sindelar
9c6af7fc03 Docs: Add job posting link to README 2021-04-26 12:16:35 -04:00
Tom Burgin
543b1a29fe add default provisioning profile rules (#548) 2021-04-19 17:18:18 -04:00
Tom Burgin
625ec67789 handle PHONE_REGISTRATION_ERROR (#549) 2021-04-19 17:16:53 -04:00
Tom Burgin
c5696d71e7 add build release rule (#547) 2021-04-19 13:58:59 -04:00
Tom Burgin
5f3cef52de cleanup (#546) 2021-04-19 13:37:21 -04:00
Tom Burgin
eeed0b5aa6 santactl: migrate from fcmstream to fcmconnection (#545) 2021-04-19 11:51:32 -04:00
Russell Hancox
9ef171e663 Docs: Fix more broken docs links (#543) 2021-04-19 11:17:13 -04:00
Russell Hancox
ad1868a50f santad: Fix transitive rules when using the sysx cache feature (#540)
This fixes transitive allowlisting when `EnableSysxCache` is turned on, reduces the deadline timer to fire 5s before the ES deadline, remaps our DEBUG logs to NOTICE so they can be more easily seen in Console and prevents transitive rules being created for paths under /dev/.
2021-03-04 09:47:32 -05:00
Russell Hancox
78643d3c49 fileinfo: Don't use non-bundle dirs as possible ancestors (#537) 2021-02-01 11:09:32 -05:00
Russell Hancox
8b22c85a64 Project: run buildifier on BUILD files (#534) 2021-01-28 10:31:07 -05:00
Russell Hancox
58fe5d3d76 santad: Use OS_FALLTHROUGH (#535) 2021-01-28 10:30:47 -05:00
Russell Hancox
8b2227967e santad: Fix caching of deny decisions (#533) 2021-01-28 10:12:20 -05:00
Russell Hancox
65693acea1 Docs: fix syncing-overview link in santactl doc (#531) 2021-01-27 12:35:02 -05:00
Russell Hancox
7cea383930 Docs: the docs build can't use symlinks ref. out of the docs dir (#530) 2021-01-27 12:25:50 -05:00
headmin
5ae2376158 Docs: Add example .mobileconfig profile to enable Notifications settings (#529) 2021-01-27 11:00:34 -05:00
Russell Hancox
e851337eac Docs: Fix some broken links in the index (#528) 2021-01-27 10:32:30 -05:00
Russell Hancox
2e53834980 santactl/sync: retry individual requests during a sync (#526)
Each request is retried up to 5 times with gaps of 2s, 4s, 6s, 8s
2021-01-26 15:58:52 -05:00
Hugh Neale
aef139e93c The configuration key "enabled_transitive_rules" should be "enable_transitive_rules" (#525) 2021-01-26 14:20:15 -05:00
Russell Hancox
a9e5bf09a7 santad: Add some TODOs related to cache 2021-01-11 13:16:38 -05:00
Russell Hancox
4ee3f281c3 santactl/status: Output cache details for sysx 2021-01-11 13:16:38 -05:00
Russell Hancox
462ce89d42 Project: Fix test locations 2021-01-11 13:16:38 -05:00
Russell Hancox
44117833c0 Project: Fix build rule 2021-01-11 13:16:38 -05:00
Russell Hancox
8b6e029da2 Project: bump version to 2021.1
This is a new versioning scheme.
2021-01-11 13:16:38 -05:00
Russell Hancox
f183e246df santad: Make use of caching endpoint security optional 2021-01-11 13:16:38 -05:00
Russell Hancox
c60a35f280 santad: Add caching layer to EndpointSecurity
This first commit is very rough, just adding the caching as simply as
possible. Refactoring is needed.
2021-01-11 13:16:38 -05:00
Russell Hancox
4f65965277 santactl/fileinfo: Fix fileinfo tests on BigSur + multiarch plists (#523)
The fileinfo tests didn't work on BigSur because of some path and binary changes.

Also, the embeddedPlist method didn't work on fat binaries, of which there are now
many, because of M1 machines. I think we didn't notice this before because we pull
the embedded plist from the first arch listed in the headers dict which generally
seemed to pick x86_64 first but with the arm64/arm64e option being added
that now appears first.

Also fixed some errors handling 32-bit segment/sections and added a test for this.
2021-01-07 19:46:48 -05:00
Tom Burgin
01e4e15b81 santactl sync: add config option to enable legacy zlib content encoding (#522) 2020-12-23 10:36:39 -05:00
Russell Hancox
532cb37e0b CI: split out driver and userspace builds (#521) 2020-12-23 08:38:39 -05:00
Tom Burgin
9d379d3884 release: split out the kext into a separate release label (#520)
* fix SNTLoggingKernel BUILD rule (#518)

* release: split out santa-driver.kext

* release: update ci

* remove ipa script rule

* update ci
2020-12-19 18:23:54 -05:00
Tom Burgin
3e7a191bf7 fix SNTLoggingKernel BUILD rule (#518) 2020-12-17 16:35:13 -05:00
Ryan Diers
c5a048f4d9 santactl/sync: Use deflate as Content-Encoding instead of zlib
The latter was not standards-compliant.
2020-12-14 16:19:48 -05:00
Hugh Neale
f4769bad90 Added Zercurity to list of available sync servers (#511) 2020-12-08 20:29:28 -05:00
Russell Hancox
254497ad15 Project: don't reference obsolete rake commands in CONTRIBUTING (#513) 2020-12-08 09:57:42 -05:00
avanzini
0a83445838 Log pidversion along with pid. (#512) 2020-12-08 09:46:34 -05:00
Tom Burgin
eff287259e project: update Xcode project to build universal binaries (#509) 2020-11-17 16:18:16 -05:00
Russell Hancox
6f2c0e3457 Project: remove Travis, update CI status in README (#508) 2020-11-02 09:59:35 -05:00
Russell Hancox
38769f7cd1 Project: Add GitHub Actions CI workflow (#507) 2020-10-30 12:23:01 -04:00
Russell Hancox
fa785ad3c2 Kernel: fix some header imports (#505) 2020-10-26 10:05:25 -04:00
Russell Hancox
5dae0cabdd Project: fix some lint (#504) 2020-10-22 14:01:32 -04:00
Russell Hancox
a8b4f4ea7e Project: move travis to xcode12 (#503) 2020-10-22 13:50:32 -04:00
44 changed files with 1451 additions and 228 deletions

25
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: CI
on:
push:
paths-ignore:
- 'docs/**'
branches:
- '*'
pull_request:
paths-ignore:
- 'docs/**'
branches:
- main
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Build Userspace
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
- name: Build Driver
run: bazel build --apple_generate_dsym -c opt :release_driver --define=SANTA_BUILD_TYPE=ci
- name: Test
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci

View File

@@ -1,15 +0,0 @@
---
os: osx
osx_image: xcode11
language: objective-c
sudo: false
addons:
homebrew:
taps: bazelbuild/tap
packages: bazelbuild/tap/bazel
update: true
script:
- bazel build :release --show_progress_rate_limit=30.0 -c opt --apple_generate_dsym --color=no --verbose_failures --sandbox_debug
- bazel test :unit_tests --show_progress_rate_limit=30.0 --test_output=errors --color=no --verbose_failures --sandbox_debug

78
BUILD
View File

@@ -15,6 +15,20 @@ apple_bundle_version(
short_version_string = SANTA_VERSION,
)
# Used to detect release builds
config_setting(
name = "release_build",
values = {"define": "SANTA_BUILD_TYPE=release"},
visibility = [":santa_package_group"],
)
# Used to detect CI builds
config_setting(
name = "ci_build",
values = {"define": "SANTA_BUILD_TYPE=ci"},
visibility = [":santa_package_group"],
)
# Used to detect optimized builds
config_setting(
name = "opt_build",
@@ -60,9 +74,9 @@ set -e
rm -rf /tmp/bazel_santa_reload
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa_driver/santa_driver.zip >/dev/null
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa/Santa.zip >/dev/null
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa/Santa.zip >/dev/null
echo "You may be asked for your password for sudo"
sudo BINARIES=/tmp/bazel_santa_reload CONF=$${BUILD_WORKSPACE_DIRECTORY}/Conf \
$${BUILD_WORKSPACE_DIRECTORY}/Conf/install.sh
@@ -78,7 +92,6 @@ genrule(
name = "release",
srcs = [
"//Source/santa:Santa",
"//Source/santa_driver",
"Conf/install.sh",
"Conf/uninstall.sh",
"Conf/com.google.santa.bundleservice.plist",
@@ -97,9 +110,9 @@ genrule(
echo "Please add '-c opt' flag to bazel invocation"
""",
":opt_build": """
# Extract santa_driver.zip and Santa.zip
# Extract Santa.zip
for SRC in $(SRCS); do
if [ "$$(basename $${SRC})" == "santa_driver.zip" -o "$$(basename $${SRC})" == "Santa.zip" ]; then
if [ "$$(basename $${SRC})" == "Santa.zip" ]; then
mkdir -p $(@D)/binaries
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
fi
@@ -116,10 +129,6 @@ genrule(
# Gather together the dSYMs. Throw an error if no dSYMs were found
for SRC in $(SRCS); do
case $${SRC} in
*santa-driver.kext.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santa-driver.kext.dSYM
;;
*santad.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santad.dSYM
@@ -162,12 +171,61 @@ genrule(
heuristic_label_expansion = 0,
)
genrule(
name = "release_driver",
srcs = [
"//Source/santa_driver",
],
outs = ["santa-driver-" + SANTA_VERSION + ".tar.gz"],
cmd = select({
"//conditions:default": """
echo "ERROR: Trying to create a release tarball without optimization."
echo "Please add '-c opt' flag to bazel invocation"
""",
":opt_build": """
# Extract santa_driver.zip
for SRC in $(SRCS); do
if [ "$$(basename $${SRC})" == "santa_driver.zip" ]; then
mkdir -p $(@D)/binaries
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
fi
done
# Gather together the dSYMs. Throw an error if no dSYMs were found
for SRC in $(SRCS); do
case $${SRC} in
*santa-driver.kext.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santa-driver.kext.dSYM
;;
esac
done
# Cause a build failure if the dSYMs are missing.
if [[ ! -d "$(@D)/dsym" ]]; then
echo "dsym dir missing: Did you forget to use --apple_generate_dsym?"
echo "This flag is required for the 'release' target."
exit 1
fi
# Update all the timestamps to now. Bazel avoids timestamps to allow
# builds to be hermetic and cacheable but for releases we want the
# timestamps to be more-or-less correct.
find $(@D)/{binaries,dsym} -exec touch {} \\;
# Create final output tar
tar -C $(@D) -czpf $(@) binaries dsym
""",
}),
heuristic_label_expansion = 0,
)
test_suite(
name = "unit_tests",
tests = [
"//Source/common:SantaCacheTest",
"//Source/common:SNTFileInfoTest",
"//Source/common:SNTPrefixTreeTest",
"//Source/santa_driver:SantaCacheTest",
"//Source/santactl:SNTCommandFileInfoTest",
"//Source/santactl:SNTCommandSyncTest",
"//Source/santad:SNTEventTableTest",

View File

@@ -1,37 +0,0 @@
Want to contribute? Great! First, read this page (including the small print at the end).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
Before you start working on a larger contribution, you should get in touch with
us first through the [issue tracker](https://github.com/google/santa/issues)
with your idea so that we can help out and possibly guide you. Coordinating up
front makes it much easier to avoid frustration later on.
### Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. It's also a good idea to run the
tests beforehand, which you can do with the following commands:
```sh
rake tests:logic
rake tests:kernel # only necessary if you're changing the kext code
```
### Code Style
All code submissions should try to match the surrounding code. Wherever possible,
code should adhere to either the
[Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.xml)
or the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).
### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).

1
CONTRIBUTING.md Symbolic link
View File

@@ -0,0 +1 @@
docs/development/contributing.md

View File

@@ -23,7 +23,7 @@ DEPENDENCIES:
- OCMock
SPEC REPOS:
https://github.com/cocoapods/specs.git:
https://github.com/CocoaPods/Specs.git:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
@@ -43,4 +43,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: d03767a9915896232523962c98d9ff7294aec2b7
COCOAPODS: 1.7.5
COCOAPODS: 1.10.0

View File

@@ -1,9 +1,4 @@
# Santa [![Build Status][build-status-img]][build-status-link] [![Documentation Status][doc-status-img]][doc-status-link]
[build-status-img]: https://travis-ci.org/google/santa.png?branch=master
[build-status-link]: https://travis-ci.org/google/santa
[doc-status-img]: https://readthedocs.org/projects/santa/badge/?version=latest
[doc-status-link]: https://santa.readthedocs.io/en/latest/?badge=latest
# Santa ![CI](https://github.com/google/santa/workflows/CI/badge.svg?branch=main)
<p align="center">
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
@@ -20,6 +15,10 @@ It is named Santa because it keeps track of binaries that are naughty or nice.
Santa is a project of Google's Macintosh Operations Team.
# We're hiring!
Want to work on Santa at Google NYC? [Apply on our Careers page!](https://goo.gle/3tO060z)
# Docs
The Santa docs are stored in the
@@ -145,6 +144,9 @@ protect hosts in whatever other ways you see fit.
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
centralized service that pulls data from multiple sources and deploy
configurations to multiple services.
* [Zercurity](https://github.com/zercurity/zercurity) - A dockerized service
for managing and monitoring applications across a large fleet utilizing
Santa + Osquery.
* Alternatively, `santactl` can configure rules locally (without a sync
server).

View File

@@ -102,6 +102,8 @@
C7CDA6FE233E73ED0013622B /* SNTXPCNotifierInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658ACF2322B84F00F36578 /* SNTXPCNotifierInterface.m */; };
C7CDA6FF233E80160013622B /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658AD82322B84F00F36578 /* SNTLogging.m */; };
C7CDA700233EB21A0013622B /* SNTXPCBundleServiceInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658ACC2322B84F00F36578 /* SNTXPCBundleServiceInterface.m */; };
C7CF38D3262DCD1800B0ABA7 /* SNTCommandSyncFCM.m in Sources */ = {isa = PBXBuildFile; fileRef = C7CF38D1262DCD1800B0ABA7 /* SNTCommandSyncFCM.m */; };
C7CF38EB262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = C7CF38E9262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm */; };
C7D35DE42322C99B000C5EB4 /* SantaDecisionManager.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7658B002322B84F00F36578 /* SantaDecisionManager.cc */; };
C7D35DE52322C99E000C5EB4 /* SantaDriver.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7658AFF2322B84F00F36578 /* SantaDriver.cc */; };
C7D35DE62322C9A1000C5EB4 /* SantaDriverClient.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7658AFA2322B84F00F36578 /* SantaDriverClient.cc */; };
@@ -115,6 +117,8 @@
C7FA384824B6169400D192F9 /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658AD82322B84F00F36578 /* SNTLogging.m */; };
C7FA384924B6169400D192F9 /* SNTStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658ADC2322B84F00F36578 /* SNTStoredEvent.m */; };
C7FA384A24B6169400D192F9 /* SNTXPCSyncServiceInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FA383724B60FF800D192F9 /* SNTXPCSyncServiceInterface.m */; };
C7FACBFA25646FE500CCB198 /* SNTConfigurator.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658ACD2322B84F00F36578 /* SNTConfigurator.m */; };
C7FACC0225646FEB00CCB198 /* SNTSystemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658AD12322B84F00F36578 /* SNTSystemInfo.m */; };
D28CA4C618C62392319BB642 /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B7714ABC7F247685608DACE7 /* libPods-santactl.a */; };
D698C8C9E47554577ED4939F /* libPods-com.google.santa.daemon.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C05F6AD95EB704B20828BDA1 /* libPods-com.google.santa.daemon.a */; };
F5F5D1EF2AF051FEA97A3A59 /* libPods-sysx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 91FF0B4E62F1E90A88478993 /* libPods-sysx.a */; };
@@ -376,6 +380,10 @@
C7A8308022F0F81F00F856AC /* com.google.santa.daemon.systemextension */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = com.google.santa.daemon.systemextension; sourceTree = BUILT_PRODUCTS_DIR; };
C7CC458F24B6184F0018C05C /* SNTXPCSyncdInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTXPCSyncdInterface.h; sourceTree = "<group>"; };
C7CC459024B6184F0018C05C /* SNTXPCSyncdInterface.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTXPCSyncdInterface.m; sourceTree = "<group>"; };
C7CF38D1262DCD1800B0ABA7 /* SNTCommandSyncFCM.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncFCM.m; sourceTree = "<group>"; };
C7CF38D2262DCD1800B0ABA7 /* SNTCommandSyncFCM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncFCM.h; sourceTree = "<group>"; };
C7CF38E9262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SNTCachingEndpointSecurityManager.mm; sourceTree = "<group>"; };
C7CF38EA262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCachingEndpointSecurityManager.h; sourceTree = "<group>"; };
C7D35DDA2322C902000C5EB4 /* santa-driver.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "santa-driver.kext"; sourceTree = BUILT_PRODUCTS_DIR; };
C7F5C1A7233E72BC00A3F7FD /* santabundleservice */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = santabundleservice; sourceTree = BUILT_PRODUCTS_DIR; };
C7FA383324B60F3300D192F9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -466,6 +474,8 @@
0D3715F9233E680700BB624A /* EventProviders */ = {
isa = PBXGroup;
children = (
C7CF38EA262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.h */,
C7CF38E9262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm */,
C7658A632322B84F00F36578 /* SNTDriverManager.h */,
C7658A742322B84F00F36578 /* SNTDriverManager.m */,
C72ED2B42324962400255555 /* SNTEndpointSecurityManager.h */,
@@ -590,6 +600,8 @@
C7658AA22322B84F00F36578 /* SNTCommandSyncConstants.m */,
C7658A982322B84F00F36578 /* SNTCommandSyncEventUpload.h */,
C7658AA12322B84F00F36578 /* SNTCommandSyncEventUpload.m */,
C7CF38D2262DCD1800B0ABA7 /* SNTCommandSyncFCM.h */,
C7CF38D1262DCD1800B0ABA7 /* SNTCommandSyncFCM.m */,
C7658A9E2322B84F00F36578 /* SNTCommandSyncManager.h */,
C7658AA92322B84F00F36578 /* SNTCommandSyncManager.m */,
C7658A9D2322B84F00F36578 /* SNTCommandSyncPostflight.h */,
@@ -1229,6 +1241,7 @@
C7658B492322C18500F36578 /* SNTCommandCacheHistogram.m in Sources */,
C7658B472322C17D00F36578 /* SNTCommandController.m in Sources */,
C7658B5D2322C2B800F36578 /* SNTFileInfo.m in Sources */,
C7CF38D3262DCD1800B0ABA7 /* SNTCommandSyncFCM.m in Sources */,
C7658B542322C1A400F36578 /* SNTCommandSyncEventUpload.m in Sources */,
C7658B5F2322C2C700F36578 /* SNTStoredEvent.m in Sources */,
C7658B4F2322C19700F36578 /* SNTCommandStatus.m in Sources */,
@@ -1283,6 +1296,7 @@
files = (
C7CC459224B6188B0018C05C /* SNTXPCSyncdInterface.m in Sources */,
C7658B332322C08B00F36578 /* SNTRule.m in Sources */,
C7CF38EB262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm in Sources */,
C7658B1D2322BFFA00F36578 /* main.m in Sources */,
0D9F577C2342650F005D9AA8 /* SNTPrefixTree.cc in Sources */,
C7658B282322C02300F36578 /* SNTDriverManager.m in Sources */,
@@ -1341,7 +1355,9 @@
C7CDA6FC233E73D80013622B /* SNTFileInfo.m in Sources */,
C7CDA6FF233E80160013622B /* SNTLogging.m in Sources */,
C7CDA6FD233E73E40013622B /* SNTStoredEvent.m in Sources */,
C7FACBFA25646FE500CCB198 /* SNTConfigurator.m in Sources */,
C7CDA6FE233E73ED0013622B /* SNTXPCNotifierInterface.m in Sources */,
C7FACC0225646FEB00CCB198 /* SNTSystemInfo.m in Sources */,
C7F5C1AE233E72CC00A3F7FD /* main.m in Sources */,
C7F5C1AF233E72CF00A3F7FD /* SNTBundleService.m in Sources */,
);
@@ -1453,7 +1469,6 @@
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;

View File

@@ -1,8 +1,23 @@
load("//:helper.bzl", "santa_unit_test")
package(default_visibility = ["//:santa_package_group"])
licenses(["notice"]) # Apache 2.0
load("//:helper.bzl", "santa_unit_test")
cc_library(
name = "SantaCache",
srcs = ["SantaCache.h"],
deps = ["//Source/common:SNTKernelCommon"],
)
santa_unit_test(
name = "SantaCacheTest",
srcs = [
"SantaCache.h",
"SantaCacheTest.mm",
],
deps = ["//Source/common:SNTKernelCommon"],
)
objc_library(
name = "SNTBlockMessage",
@@ -37,7 +52,7 @@ objc_library(
],
)
cc_library(
objc_library(
name = "SNTCommonEnums",
hdrs = ["SNTCommonEnums.h"],
)
@@ -77,6 +92,11 @@ cc_library(
cc_library(
name = "SNTLoggingKernel",
hdrs = ["SNTLogging.h"],
copts = [
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = ["KERNEL"],
)
objc_library(
@@ -209,6 +229,7 @@ santa_unit_test(
resources = [
"testdata/bad_pagezero",
"testdata/missing_pagezero",
"testdata/32bitplist",
],
structured_resources = glob([
"testdata/BundleExample.app/**",

View File

@@ -127,16 +127,13 @@
withString:config.machineID];
}
if (hostname.length) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%hostname%"
withString:hostname];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%hostname%" withString:hostname];
}
if (uuid.length) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%uuid%"
withString:uuid];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%uuid%" withString:uuid];
}
if (serial.length) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%"
withString:serial];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%" withString:serial];
}
return [NSURL URLWithString:formatStr];

View File

@@ -173,6 +173,15 @@
///
@property(readonly, nonatomic) BOOL enableSystemExtension;
///
/// Use an internal cache for decisions instead of relying on the caching
/// mechanism built-in to the EndpointSecurity framework. This may increase
/// performance, particularly when Santa is run alongside other system
/// extensions.
/// Has no effect if the system extension is not being used. Defaults to NO.
///
@property(readonly, nonatomic) BOOL enableSysxCache;
#pragma mark - GUI Settings
///
@@ -321,7 +330,8 @@
@property(readonly, nonatomic) BOOL enableForkAndExitLogging;
///
/// If true, ignore actions from other endpoint security clients. Defaults to false. This only applies when running as a sysx.
/// If true, ignore actions from other endpoint security clients. Defaults to false. This only
/// applies when running as a sysx.
///
@property(readonly, nonatomic) BOOL ignoreOtherEndpointSecurityClients;
@@ -331,6 +341,34 @@
///
@property(readonly, nonatomic) BOOL enableDebugLogging;
///
/// If true, compressed requests from "santactl sync" will set "Content-Encoding" to "zlib"
/// instead of the new default "deflate". If syncing with Upvote deployed at commit 0b4477d
/// or below, set this option to true.
/// Defaults to false.
///
@property(readonly, nonatomic) BOOL enableBackwardsCompatibleContentEncoding;
///
/// Contains the FCM project name.
///
@property(readonly, nonatomic) NSString *fcmProject;
///
/// Contains the FCM project entity.
///
@property(readonly, nonatomic) NSString *fcmEntity;
///
/// Contains the FCM project API key.
///
@property(readonly, nonatomic) NSString *fcmAPIKey;
///
/// True if fcmProject, fcmEntity and fcmAPIKey are all set. Defaults to false.
///
@property(readonly, nonatomic) BOOL fcmEnabled;
///
/// Retrieve an initialized singleton configurator object using the default file path.
///

View File

@@ -79,11 +79,17 @@ static NSString *const kEventLogPath = @"EventLogPath";
static NSString *const kEnableMachineIDDecoration = @"EnableMachineIDDecoration";
static NSString *const kEnableSystemExtension = @"EnableSystemExtension";
static NSString *const kEnableSysxCache = @"EnableSysxCache";
static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
static NSString *const kIgnoreOtherEndpointSecurityClients = @"IgnoreOtherEndpointSecurityClients";
static NSString *const kEnableDebugLogging = @"EnableDebugLogging";
static NSString *const kEnableBackwardsCompatibleContentEncoding = @"EnableBackwardsCompatibleContentEncoding";
static NSString *const kFCMProject = @"FCMProject";
static NSString *const kFCMEntity = @"FCMEntity";
static NSString *const kFCMAPIKey = @"FCMAPIKey";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
@@ -131,7 +137,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey: number,
kEnableBadSignatureProtectionKey : number,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
@@ -144,7 +150,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
kClientAuthCertificateIssuerKey : string,
kServerAuthRootsDataKey : data,
kServerAuthRootsDataKey : data,
kServerAuthRootsFileKey : string,
kMachineOwnerKey : string,
kMachineIDKey : string,
@@ -156,9 +162,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kEventLogPath : string,
kEnableMachineIDDecoration : number,
kEnableSystemExtension : number,
kEnableSysxCache : number,
kEnableForkAndExitLogging : number,
kIgnoreOtherEndpointSecurityClients : number,
kEnableDebugLogging: number,
kEnableDebugLogging : number,
kEnableBackwardsCompatibleContentEncoding : number,
kFCMProject : string,
kFCMEntity : string,
kFCMAPIKey : string,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -330,6 +341,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableForkAndExitLogging {
return [self configStateSet];
}
@@ -342,6 +357,26 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmEntity {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmAPIKey {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmEnabled {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -577,6 +612,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
- (BOOL)enableSysxCache {
NSNumber *number = self.configState[kEnableSysxCache];
return number ? [number boolValue] : NO;
}
- (BOOL)enableForkAndExitLogging {
NSNumber *number = self.configState[kEnableForkAndExitLogging];
return number ? [number boolValue] : NO;
@@ -592,6 +632,27 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [number boolValue] || self.debugFlag;
}
- (BOOL)enableBackwardsCompatibleContentEncoding {
NSNumber *number = self.configState[kEnableBackwardsCompatibleContentEncoding];
return number ? [number boolValue] : NO;
}
- (NSString *)fcmProject {
return self.configState[kFCMProject];
}
- (NSString *)fcmEntity {
return self.configState[kFCMEntity];
}
- (NSString *)fcmAPIKey {
return self.configState[kFCMAPIKey];
}
- (BOOL)fcmEnabled {
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
}
#pragma mark Private
///

View File

@@ -362,7 +362,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
while (pathComponents.count > 1) {
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if ([bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
if (!ancestor ||
if ((!ancestor && bndl.bundlePath.pathExtension.length) ||
[[self allowedAncestorExtensions] containsObject:bndl.bundlePath.pathExtension]) {
bundle = bndl;
}
@@ -551,24 +551,51 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
for (uint32_t i = 0; i < ncmds; ++i) {
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
if (!cmdData) return nil;
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
if (is64) {
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
if (lc->cmd == LC_SEGMENT_64 && memcmp(lc->segname, "__TEXT", 6) == 0) {
nsects = lc->nsects;
offset += sz_segment;
break;
}
offset += lc->cmdsize;
} else {
struct segment_command *lc = (struct segment_command *)[cmdData bytes];
if (lc->cmd == LC_SEGMENT && memcmp(lc->segname, "__TEXT", 6) == 0) {
nsects = lc->nsects;
offset += sz_segment;
break;
}
offset += lc->cmdsize;
}
offset += lc->cmdsize;
}
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
for (uint32_t i = 0; i < nsects; ++i) {
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
if (!sectData) return nil;
struct section_64 *sect = (struct section_64 *)[sectData bytes];
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
uint64_t sectoffset, sectsize = 0;
BOOL found = NO;
if (is64) {
struct section_64 *sect = (struct section_64 *)[sectData bytes];
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
sectoffset = sect->offset;
sectsize = sect->size;
found = YES;
}
} else {
struct section *sect = (struct section *)[sectData bytes];
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
sectoffset = sect->offset;
sectsize = sect->size;
found = YES;
}
}
if (found) {
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(mhwo.offset + sectoffset,
sectsize)];
if (!plistData) return nil;
NSDictionary *plist;
plist = [NSPropertyListSerialization propertyListWithData:plistData

View File

@@ -39,9 +39,8 @@
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
XCTAssertEqualObjects(sut.path, @"/bin/ls");
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/AppleFileServer"];
XCTAssertEqualObjects(sut.path, @"/System/Library/CoreServices/AppleFileServer.app/"
@"Contents/MacOS/AppleFileServer");
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/DirectoryService"];
XCTAssertEqualObjects(sut.path, @"/usr/libexec/dspluginhelperd");
}
- (void)testSHA1 {
@@ -72,7 +71,6 @@
XCTAssertTrue(sut.isExecutable);
XCTAssertFalse(sut.isDylib);
XCTAssertFalse(sut.isFat);
XCTAssertFalse(sut.isKext);
XCTAssertFalse(sut.isScript);
}
@@ -106,7 +104,7 @@
}
- (void)testDylibs {
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/libsqlite3.dylib"];
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/system/libsystem_platform.dylib"];
XCTAssertTrue(sut.isMachO);
XCTAssertTrue(sut.isDylib);
@@ -231,10 +229,16 @@
}
- (void)testEmbeddedInfoPlist {
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"32bitplist"
ofType:@""];
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
XCTAssertNotNil([sut infoPlist]);
XCTAssertEqualObjects([sut infoPlist][@"CFBundleShortVersionString"], @"1.0");
XCTAssertEqualObjects([sut infoPlist][@"CFBundleIdentifier"], @"com.google.i386plist");
// csreq is installed on all machines with Xcode installed. If you're running these tests,
// it should be available..
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
XCTAssertNotNil([sut infoPlist]);
}

View File

@@ -16,11 +16,12 @@
/// Common defines between kernel <-> userspace
///
#include <sys/param.h>
#ifndef SANTA__COMMON__KERNELCOMMON_H
#define SANTA__COMMON__KERNELCOMMON_H
#include <stdint.h>
#include <sys/param.h>
// Defines the name of the userclient class and the driver bundle ID.
#define USERCLIENT_CLASS "com_google_SantaDriver"
#define USERCLIENT_ID "com.google.santa-driver"
@@ -118,6 +119,7 @@ typedef struct {
uid_t uid;
gid_t gid;
pid_t pid;
int pidversion;
pid_t ppid;
char path[MAXPATHLEN];
char newpath[MAXPATHLEN];

View File

@@ -88,7 +88,10 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
break;
case LOG_LEVEL_DEBUG:
levelName = "D";
syslogLevel = ASL_LEVEL_DEBUG;
// Log debug messages at the same ASL level as INFO.
// While it would make sense to use DEBUG, watching debug-level logs
// in Console means enabling all debug logs, which is absurdly noisy.
syslogLevel = ASL_LEVEL_NOTICE;
break;
}

View File

@@ -18,7 +18,7 @@
#include <string>
#include <vector>
#include "Source/santa_driver/SantaCache.h"
#include "Source/common/SantaCache.h"
@interface SantaCacheTest : XCTestCase
@end

BIN
Source/common/testdata/32bitplist vendored Executable file

Binary file not shown.

View File

@@ -1,11 +1,11 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
licenses(["notice"]) # Apache 2.0
exports_files([
"Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-256.png",
])
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
objc_library(
name = "SantaGUI_lib",
srcs = [
@@ -55,6 +55,10 @@ macos_application(
bundle_name = "Santa",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":SantaGUI_lib"],

View File

@@ -1,17 +1,16 @@
licenses(["notice"]) # Apache 2.0
load(
"@build_bazel_rules_apple//apple:macos.bzl",
"macos_command_line_application",
"macos_kernel_extension",
)
load("//:helper.bzl", "run_command", "santa_unit_test")
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
licenses(["notice"])
cc_library(
name = "santa_driver_lib",
srcs = [
"SantaCache.h",
"SantaDecisionManager.cc",
"SantaDecisionManager.h",
"SantaDriver.cc",
@@ -33,6 +32,7 @@ cc_library(
"SANTA_VERSION=" + SANTA_VERSION,
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLoggingKernel",
"//Source/common:SNTPrefixTreeKernel",
@@ -40,15 +40,6 @@ cc_library(
alwayslink = 1,
)
santa_unit_test(
name = "SantaCacheTest",
srcs = [
"SantaCache.h",
"SantaCacheTest.mm",
],
deps = ["//Source/common:SNTKernelCommon"],
)
macos_kernel_extension(
name = "santa_driver",
bundle_id = "com.google.santa-driver",

View File

@@ -23,10 +23,10 @@
#include <sys/proc.h>
#include <sys/vnode.h>
#include "Source/common/SantaCache.h"
#include "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTLogging.h"
#include "Source/common/SNTPrefixTree.h"
#include "Source/santa_driver/SantaCache.h"
///
/// SantaDecisionManager is responsible for intercepting Vnode execute actions

View File

@@ -46,6 +46,7 @@ objc_library(
sdk_dylibs = ["libz"],
sdk_frameworks = ["IOKit"],
deps = [
":FCM_lib",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
@@ -64,11 +65,20 @@ objc_library(
"@FMDB",
"@MOLAuthenticatingURLSession",
"@MOLCodesignChecker",
"@MOLFCMClient",
"@MOLXPCConnection",
],
)
objc_library(
name = "FCM_lib",
srcs = ["Commands/sync/SNTCommandSyncFCM.m"],
hdrs = ["Commands/sync/SNTCommandSyncFCM.h"],
sdk_frameworks = ["SystemConfiguration"],
deps = [
"@MOLAuthenticatingURLSession",
],
)
macos_command_line_application(
name = "santactl",
bundle_id = "com.google.santa.ctl",
@@ -130,6 +140,7 @@ santa_unit_test(
],
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
@@ -140,7 +151,6 @@ santa_unit_test(
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLFCMClient",
"@MOLXPCConnection",
"@OCMock",
],

View File

@@ -86,9 +86,11 @@ REGISTER_COMMAND_NAME(@"status")
SNTConfigurator *configurator = [SNTConfigurator configurator];
BOOL cachingEnabled = (![configurator enableSystemExtension] || [configurator enableSysxCache]);
// Kext status
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
if (![configurator enableSystemExtension]) {
if (cachingEnabled) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] cacheCounts:^(uint64_t rootCache, uint64_t nonRootCache) {
rootCacheCount = rootCache;
@@ -206,8 +208,8 @@ REGISTER_COMMAND_NAME(@"status")
@"transitive_rules" : @(enableTransitiveRules),
},
} mutableCopy];
if (![configurator enableSystemExtension]) {
stats[@"kernel"] = @{
if (cachingEnabled) {
stats[@"cache"] = @{
@"root_cache_count" : @(rootCacheCount),
@"non_root_cache_count": @(nonRootCacheCount),
};
@@ -224,8 +226,8 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
if (![configurator enableSystemExtension]) {
printf(">>> Kernel Info\n");
if (cachingEnabled) {
printf(">>> Cache Info\n");
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
}

View File

@@ -0,0 +1,115 @@
/// Copyright 2021 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>
/** A block that takes a NSString object as an argument. */
typedef void (^SNTCommandSyncFCMTokenHandler)(NSString *);
/** A block that takes a NSDictionary object as an argument. */
typedef void (^SNTCommandSyncFCMMessageHandler)(NSDictionary *);
/** A block that takes a NSHTTPURLResponse and NSError object as an argument. */
typedef void (^SNTCommandSyncFCMConnectionErrorHandler)(NSHTTPURLResponse *, NSError *);
/** A block that takes a NSDictionary and NSError object as arguments. */
typedef void (^SNTCommandSyncFCMAcknowledgeErrorHandler)(NSDictionary *, NSError *);
@interface SNTCommandSyncFCM : NSObject
/** Returns YES if connected to FCM. */
@property(readonly, nonatomic) BOOL isConnected;
/** A block to be executed when the FCM token changes */
@property(copy) SNTCommandSyncFCMTokenHandler tokenHandler;
/** A block to be executed when there is an issue with acknowledging a message. */
@property(copy) SNTCommandSyncFCMAcknowledgeErrorHandler acknowledgeErrorHandler;
/** A block to be executed when there is a non-recoverable issue with the FCM Connection. */
@property(copy) SNTCommandSyncFCMConnectionErrorHandler connectionErrorHandler;
- (instancetype)init NS_UNAVAILABLE;
/**
* The designated initializer.
*
* @param project FCM project
* @param entity FCM entity
* @param apiKey FCM apiKey
* @param connectDelayMax Optional, max delay (seconds) when calling connect
* @param backoffMax Optional, max backoff (seconds) when the connection is interrupted
* @param fatalCodes Optional, do not attempt to reconnect if a fatal code is returned
* @param sessionConfiguration Optional, the desired NSURLSessionConfiguration
* @param messageHandler The block to be called for every message received
*
* @note If the host argument is nil, https://fcm-stream.googleapis.com will be used.
* @note If the connectDelayMax argument is 0, a default value of 10 will be used.
* @note If the backoffMax argument is 0, a default value of 900 will be used.
* @note If the fatalCodes argument is nil, @[@302, @400, @403] will be used.
* @note If the sessionConfiguration argument is nil, defaultSessionConfiguration will be used.
*
* @return An initialized SNTCommandSyncFCM object
*/
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
connectDelayMax:(uint32_t)connectDelayMax
backoffMax:(uint32_t)backoffMax
fatalCodes:(NSArray<NSNumber *> *)fatalCodes
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler
NS_DESIGNATED_INITIALIZER;
/** A convenience initializer. Optional args will use their zero values. */
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler;
/** A convenience initializer. Optional args will use their zero values. */
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler;
/**
* Opens a connection to FCM and starts listening for messages.
*
* @note A random delay will occur before the connection is made.
* @note If there is a failure in the connection, reconnection will occur once FCM is reachable.
* Failed reconnections will backoff exponentially up to the defined max.
*/
- (void)connect;
/**
* Acknowledges a FCM message. Each message received must be acknowledged.
*
* @param message A FCM message
*
* @note Calls the acknowledgeErrorHandler block property when an acknowledge error occurs.
*/
- (void)acknowledgeMessage:(NSDictionary *)message;
/**
* Closes all FCM connections. Stops Reachability. Outstanding tasks will be canceled.
*
* @note After disconnect is called the receiver is considered dead. A new MOLFCMClient object
* will need to be created to begin listening for messages.
* @note After disconnect the receiver can hold a reference to itself for up to 15 minutes.
*/
- (void)disconnect;
@end

View File

@@ -0,0 +1,480 @@
/// Copyright 2021 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 "Source/santactl/Commands/sync/SNTCommandSyncFCM.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#ifdef DEBUG
#define LOGD(format, ...) NSLog(format, ##__VA_ARGS__);
#else // DEBUG
#define LOGD(format, ...)
#endif // DEBUG
/** FCM checkin and register components */
static NSString *const kFCMCheckinHost = @"https://android.clients.google.com";
static NSString *const kFCMCheckin = @"/checkin";
static NSString *const kFCMCheckinBody = @"{'checkin':{}, 'version':3}";
static NSString *const kFCMRegister = @"/c2dm/register3";
/** FCM connect and ack components */
static NSString *const kFCMConnectHost = @"https://fcmconnection.googleapis.com";
static NSString *const kFCMConnect = @"/v1alpha1:connectDownstream";
static NSString *const kFCMAck = @"/v1alpha1:ack";
/** FCM client keys */
static NSString *const kAndroidIDKey = @"android_id";
static NSString *const kVersionInfoKey = @"version_info";
static NSString *const kSecurityTokenKey = @"security_token";
/** HTTP Header Constants */
static NSString *const kFCMApplicationForm = @"application/x-www-form-urlencoded";
static NSString *const kFCMApplicationJSON = @"application/json";
static NSString *const kFCMContentType = @"Content-Type";
/** Default 15 minute backoff maximum */
static const uint32_t kDefaultBackoffMaxSeconds = 900;
/** Default 10 sec connect delay maximum */
static const uint32_t kDefaultConnectDelayMaxSeconds = 10;
#pragma mark MOLFCMClient Extension
@interface SNTCommandSyncFCM () {
/** URL components for client registration, receiving and acknowledging messages. */
NSURLComponents *_checkinComponents;
NSURLComponents *_registerComponents;
NSURLComponents *_connectComponents;
NSURLComponents *_ackComponents;
/** Holds the NSURLSession object generated by the MOLAuthenticatingURLSession object. */
NSURLSession *_session;
/** Holds the current backoff seconds. */
uint32_t _backoffSeconds;
/** Holds the max connect and backoff seconds. */
uint32_t _connectDelayMaxSeconds;
uint32_t _backoffMaxSeconds;
NSArray<NSNumber *> *_fatalHTTPStatusCodes;
}
/** NSURLSession wrapper used for https communication with the FCM service. */
@property(nonatomic) MOLAuthenticatingURLSession *authSession;
/** The block to be called for every message. */
@property(copy, nonatomic) SNTCommandSyncFCMMessageHandler messageHandler;
/** Is used throughout the class to reconnect to FCM after a connection loss. */
@property SCNetworkReachabilityRef reachability;
/** FCM client identities. */
@property(nonatomic, readonly) NSString *project;
@property(nonatomic, readonly) NSString *entity;
@property(nonatomic, readonly) NSString *apiKey;
/** FCM client checkin data */
@property NSString *androidID;
@property NSString *versionInfo;
@property NSString *securityToken;
/** Called by the reachability handler when the host becomes reachable. */
- (void)reachabilityRestored;
@end
#pragma mark SCNetworkReachabilityCallBack
/** Called when the network state changes. */
static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
void *info) {
dispatch_async(dispatch_get_main_queue(), ^{
if (flags & kSCNetworkReachabilityFlagsReachable) {
SNTCommandSyncFCM *FCMClient = (__bridge SNTCommandSyncFCM *)info;
SEL s = @selector(reachabilityRestored);
[NSObject cancelPreviousPerformRequestsWithTarget:FCMClient selector:s object:nil];
[FCMClient performSelector:s withObject:nil afterDelay:1];
}
});
}
@implementation SNTCommandSyncFCM
#pragma mark init/dealloc methods
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
connectDelayMax:(uint32_t)connectDelayMax
backoffMax:(uint32_t)backoffMax
fatalCodes:(NSArray<NSNumber *> *)fatalCodes
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
self = [super init];
if (self) {
_project = project;
_entity = entity;
_apiKey = apiKey;
_checkinComponents = [NSURLComponents componentsWithString:kFCMCheckinHost];
_checkinComponents.path = kFCMCheckin;
_registerComponents = [NSURLComponents componentsWithString:kFCMCheckinHost];
_registerComponents.path = kFCMRegister;
_connectComponents = [NSURLComponents componentsWithString:kFCMConnectHost];
_connectComponents.path = kFCMConnect;
_ackComponents = [NSURLComponents componentsWithString:kFCMConnectHost];
_ackComponents.path = kFCMAck;
_messageHandler = messageHandler;
_authSession = [[MOLAuthenticatingURLSession alloc]
initWithSessionConfiguration:sessionConfiguration
?: [NSURLSessionConfiguration
defaultSessionConfiguration]];
_authSession.dataTaskDidReceiveDataBlock = [self dataTaskDidReceiveDataBlock];
_authSession.taskDidCompleteWithErrorBlock = [self taskDidCompleteWithErrorBlock];
_session = _authSession.session;
_connectDelayMaxSeconds = connectDelayMax ?: kDefaultConnectDelayMaxSeconds;
_backoffMaxSeconds = backoffMax ?: kDefaultBackoffMaxSeconds;
_fatalHTTPStatusCodes = fatalCodes ?: @[ @302, @400, @401, @403, @404 ];
}
return self;
}
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
return [self initWithProject:project
entity:entity
apiKey:apiKey
connectDelayMax:0
backoffMax:0
fatalCodes:nil
sessionConfiguration:sessionConfiguration
messageHandler:messageHandler];
}
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
return [self initWithProject:project
entity:entity
apiKey:apiKey
connectDelayMax:0
backoffMax:0
fatalCodes:nil
sessionConfiguration:nil
messageHandler:messageHandler];
}
/** Before this object is released ensure reachability release. */
- (void)dealloc {
[self stopReachability];
}
#pragma mark property methods
- (BOOL)isConnected {
if (!self.androidID || !self.androidID || !self.securityToken) return NO;
for (NSURLSessionDataTask *dataTask in [self dataTasks]) {
if (dataTask.state == NSURLSessionTaskStateRunning) return YES;
}
return NO;
}
#pragma mark reachability methods
- (void)reachabilityRestored {
LOGD(@"Reachability restored. Reconnect after a backoff of %i seconds", _backoffSeconds);
[self stopReachability];
dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, _backoffSeconds * NSEC_PER_SEC);
dispatch_after(t, dispatch_get_main_queue(), ^{
[self connectHelper];
});
}
/** Start listening for network state changes on a background thread. */
- (void)startReachability {
if (self.reachability) return;
LOGD(@"Reachability started.");
self.reachability =
SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, kFCMConnectHost.UTF8String);
SCNetworkReachabilityContext context = {.info = (__bridge void *)self};
if (SCNetworkReachabilitySetCallback(self.reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
self.reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
}
}
/** Stop listening for network state changes. */
- (void)stopReachability {
if (self.reachability) {
SCNetworkReachabilitySetDispatchQueue(self.reachability, NULL);
if (self.reachability) CFRelease(self.reachability);
self.reachability = NULL;
}
}
#pragma mark message methods
- (void)connect {
uint32_t ms = arc4random_uniform(_connectDelayMaxSeconds * 1000);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, ms * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
[self connectHelper];
});
}
- (void)connectHelper {
LOGD(@"Connecting...");
[self cancelConnections];
// Reuse checkin credentials / FCM token if allready registered.
if (!self.androidID || !self.androidID || !self.securityToken) return [self checkin];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_connectComponents.URL];
[URLRequest addValue:kFCMApplicationJSON forHTTPHeaderField:kFCMContentType];
URLRequest.HTTPMethod = @"GET";
[self setCheckinAuthorization:URLRequest];
[[_session dataTaskWithRequest:URLRequest] resume];
}
- (void)checkin {
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_checkinComponents.URL];
[URLRequest addValue:kFCMApplicationJSON forHTTPHeaderField:kFCMContentType];
URLRequest.HTTPMethod = @"POST";
URLRequest.HTTPBody = [kFCMCheckinBody dataUsingEncoding:NSUTF8StringEncoding];
[[_session dataTaskWithRequest:URLRequest] resume];
}
- (void)checkinDataHandler:(NSData *)data {
id jo = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
LOGD(@"checkin: %@", jo);
NSDictionary *checkin = [self extractCheckinFrom:jo];
if (!checkin) return;
self.androidID = [(NSNumber *)checkin[kAndroidIDKey] stringValue];
self.versionInfo = checkin[kVersionInfoKey];
self.securityToken = [(NSNumber *)checkin[kSecurityTokenKey] stringValue];
}
- (NSDictionary *)extractCheckinFrom:(id)jo {
if (!jo) return nil;
if (![jo isKindOfClass:[NSDictionary class]]) return nil;
if (!jo[kAndroidIDKey]) return nil;
if (![jo[kAndroidIDKey] isKindOfClass:[NSNumber class]]) return nil;
if (!jo[kVersionInfoKey]) return nil;
if (![jo[kVersionInfoKey] isKindOfClass:[NSString class]]) return nil;
if (!jo[kSecurityTokenKey]) return nil;
if (![jo[kSecurityTokenKey] isKindOfClass:[NSNumber class]]) return nil;
return jo;
}
- (void)register {
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_registerComponents.URL];
URLRequest.HTTPMethod = @"POST";
URLRequest.HTTPBody = [kFCMCheckinBody dataUsingEncoding:NSUTF8StringEncoding];
NSURLComponents *params = [[NSURLComponents alloc] init];
params.queryItems = @[
[NSURLQueryItem queryItemWithName:@"app" value:self.project],
[NSURLQueryItem queryItemWithName:@"info" value:self.versionInfo],
[NSURLQueryItem queryItemWithName:@"sender" value:self.entity],
[NSURLQueryItem queryItemWithName:@"device" value:self.androidID],
[NSURLQueryItem queryItemWithName:@"X-scope" value:@"*"],
];
URLRequest.HTTPBody = [params.query dataUsingEncoding:NSUTF8StringEncoding];
[URLRequest addValue:kFCMApplicationForm forHTTPHeaderField:kFCMContentType];
NSString *aid = [NSString stringWithFormat:@"AidLogin %@:%@", self.androidID, self.securityToken];
[URLRequest addValue:aid forHTTPHeaderField:@"Authorization"];
[[_session dataTaskWithRequest:URLRequest] resume];
}
- (void)registerDataHandler:(NSData *)data {
NSString *t = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *c = [t componentsSeparatedByString:@"="];
if (c.count == 2) {
NSString *tok = c[1];
if ([tok isEqualToString:@"PHONE_REGISTRATION_ERROR"]) {
LOGD(@"register: PHONE_REGISTRATION_ERROR - retrying");
sleep(1);
return [self register];
}
if (self.tokenHandler) self.tokenHandler(tok);
}
}
- (void)acknowledgeMessage:(NSDictionary *)message {
if (!message[@"messageId"]) return;
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_ackComponents.URL];
URLRequest.HTTPMethod = @"POST";
[URLRequest addValue:kFCMApplicationJSON forHTTPHeaderField:kFCMContentType];
[self setCheckinAuthorization:URLRequest];
NSDictionary *b = @{@"ack" : @{@"messageId" : message[@"messageId"]}};
NSData *body = [NSJSONSerialization dataWithJSONObject:b options:0 error:NULL];
URLRequest.HTTPBody = body;
[[_session dataTaskWithRequest:URLRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (((NSHTTPURLResponse *)response).statusCode != 200) {
if (self.acknowledgeErrorHandler) {
self.acknowledgeErrorHandler(message, error);
}
}
}] resume];
}
- (void)disconnect {
[self stopReachability];
[_session invalidateAndCancel];
_session = nil;
}
- (void)cancelConnections {
for (NSURLSessionDataTask *dataTask in [self dataTasks]) {
[dataTask cancel];
}
}
- (NSArray<NSURLSessionDataTask *> *)dataTasks {
__block NSArray *dataTasks;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[_session getTasksWithCompletionHandler:^(NSArray *data, NSArray *upload, NSArray *download) {
dataTasks = data;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return dataTasks;
}
/**
* Parse FCM data; extract and call self.messageHandler for each message.
*
* Expected format:
* [{
* "noOp": {}
* }
* , <-- start of new chunk
* {
* "noOp": {}
* }
*
*/
- (void)processMessagesFromData:(NSData *)data {
if (!data) return;
NSMutableString *raw = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (raw.length < 2) return;
// Add an opening bracket if this is a message in the middle of the stream.
[raw replaceOccurrencesOfString:@",\r" withString:@"[" options:0 range:NSMakeRange(0, 2)];
// Always add a closing bracket.
[raw appendString:@"]"];
NSError *err;
id jo = [NSJSONSerialization JSONObjectWithData:[raw dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&err];
if (!jo) {
if (err) LOGD(@"processMessagesFromData err: %@", err);
return;
}
LOGD(@"processMessagesFromData: %@", jo);
if (![jo isKindOfClass:[NSArray class]]) return;
for (id md in jo) {
if (![md isKindOfClass:[NSDictionary class]]) continue;
NSDictionary *m = md[@"message"];
if ([m isKindOfClass:[NSDictionary class]]) self.messageHandler(m);
}
}
- (void)handleHTTPReponse:(NSHTTPURLResponse *)HTTPResponse error:(NSError *)error {
if (HTTPResponse.statusCode == 200) {
_backoffSeconds = 0;
if ([HTTPResponse.URL.path isEqualToString:kFCMCheckin]) {
// If checkin was successful, start listening for messages and continue to register.
[self connectHelper];
return [self register];
} else if ([HTTPResponse.URL.path isEqualToString:kFCMConnect]) {
// connect will re-connect.
return [self connectHelper];
} // register may be called more than once, don't do anything in response.
} else if ([_fatalHTTPStatusCodes containsObject:@(HTTPResponse.statusCode)]) {
if (self.connectionErrorHandler) self.connectionErrorHandler(HTTPResponse, error);
} else {
// If no backoff is set, start out with 5 - 15 seconds.
// If a backoff is already set, double it, with a max of kBackoffMaxSeconds.
_backoffSeconds = _backoffSeconds * 2 ?: arc4random_uniform(11) + 5;
if (_backoffSeconds > _backoffMaxSeconds) _backoffSeconds = _backoffMaxSeconds;
if (error) LOGD(@"handleHTTPReponse err: %@", error);
[self startReachability];
}
}
- (void)setCheckinAuthorization:(NSMutableURLRequest *)URLRequest {
NSString *a = [NSString
stringWithFormat:@"checkin %@:%@ %@", self.androidID, self.securityToken, self.versionInfo];
[URLRequest addValue:a forHTTPHeaderField:@"Authorization"];
[URLRequest addValue:self.apiKey forHTTPHeaderField:@"X-Goog-Api-Key"];
}
#pragma mark NSURLSession block property and methods
/**
* MOLAuthenticatingURLSession is the NSURLSessionDelegate. It will call this block every time
* the URLSession:task:didCompleteWithError: is called. This allows MOLFCMClient to be notified
* when a task ends while using delegate methods.
*/
- (void (^)(NSURLSession *, NSURLSessionDataTask *, NSData *))dataTaskDidReceiveDataBlock {
__weak __typeof(self) weakSelf = self;
return ^(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) {
__typeof(self) strongSelf = weakSelf;
NSString *path = dataTask.originalRequest.URL.path;
if ([path isEqualToString:kFCMCheckin]) {
return [strongSelf checkinDataHandler:data];
} else if ([path isEqualToString:kFCMRegister]) {
return [strongSelf registerDataHandler:data];
} else if ([dataTask.originalRequest.URL.path isEqualToString:kFCMConnect]) {
[strongSelf processMessagesFromData:data];
}
};
}
/**
* MOLAuthenticatingURLSession is the NSURLSessionDataDelegate. It will call this block every time
* the URLSession:dataTask:didReceiveData: is called. This allows for message data chunks to be
* processed as they appear in the FCM buffer. For Content-Type: text/html there is a 512 byte
* buffer that must be filled before data is returned. Content-Type: application/json does not use
* a buffer and data is returned as soon as it is available.
*
* TODO:(bur) Follow up with FCM on Content-Type: application/json. Currently FCM returns data with
* Content-Type: text/html. Messages under 512 bytes will not be processed until the connection
* drains.
*/
- (void (^)(NSURLSession *, NSURLSessionTask *, NSError *))taskDidCompleteWithErrorBlock {
__weak __typeof(self) weakSelf = self;
return ^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
__typeof(self) strongSelf = weakSelf;
// task.response can be nil when an NSURLError* occurs
if (task.response && ![task.response isKindOfClass:[NSHTTPURLResponse class]]) {
if (strongSelf.connectionErrorHandler) strongSelf.connectionErrorHandler(nil, error);
return;
}
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)task.response;
[strongSelf handleHTTPReponse:HTTPResponse error:error];
};
}
@end

View File

@@ -15,7 +15,6 @@
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <MOLFCMClient/MOLFCMClient.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <SystemConfiguration/SystemConfiguration.h>
@@ -28,6 +27,7 @@
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncFCM.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
@@ -61,7 +61,8 @@ static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property MOLFCMClient *FCMClient;
@property SNTCommandSyncFCM *FCMClient;
@property NSString *FCMToken;
@property(nonatomic) MOLXPCConnection *daemonConn;
@@ -185,40 +186,46 @@ static void reachabilityHandler(
#pragma mark push notification methods
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
if ([self.FCMClient.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Continue with the current FCMToken");
if ([self.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Already listening for push notifications");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
self.FCMClient = [[MOLFCMClient alloc] initWithFCMToken:syncState.FCMToken
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || [message isEqual:@{}]) return;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTCommandSyncFCM alloc] initWithProject:config.fcmProject
entity:config.fcmEntity
apiKey:config.fcmAPIKey
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || message[@"noOp"]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.tokenHandler = ^(NSString *t) {
STRONGIFY(self);
LOGD(@"tokenHandler: %@", t);
self.FCMToken = t;
[self preflightOnly:YES];
};
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.FCMToken = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
LOGD(@"FCMClient: %@", log);
};
[self.FCMClient connect];
}
@@ -337,6 +344,11 @@ static void reachabilityHandler(
#pragma mark syncing chain
- (void)preflight {
[self preflightOnly:NO];
}
- (void)preflightOnly:(BOOL)preflightOnly {
LOGD(@"Preflight starting");
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
@@ -348,18 +360,19 @@ static void reachabilityHandler(
self.eventBatchSize = syncState.eventBatchSize;
// Start listening for push notifications with a full sync every FCMFullSyncInterval
if (syncState.daemon && syncState.FCMToken) {
if (syncState.daemon && [SNTConfigurator configurator].fcmEnabled) {
self.FCMFullSyncInterval = syncState.FCMFullSyncInterval;
self.FCMGlobalRuleSyncDeadline = syncState.FCMGlobalRuleSyncDeadline;
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCMToken not provided. Sync every %lu min.", syncState.fullSyncInterval / 60);
LOGD(@"FCM not enabled. Sync every %lu min.", syncState.fullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.fullSyncInterval = syncState.fullSyncInterval;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.fullSyncInterval];
}
if (preflightOnly) return;
return [self eventUploadWithSyncState:syncState];
} else {
if (!syncState.daemon) {
@@ -373,6 +386,7 @@ static void reachabilityHandler(
}
- (void)eventUploadWithSyncState:(SNTCommandSyncState *)syncState {
LOGD(@"Event upload starting");
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
@@ -384,6 +398,7 @@ static void reachabilityHandler(
}
- (void)ruleDownloadWithSyncState:(SNTCommandSyncState *)syncState {
LOGD(@"Rule download starting");
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
@@ -395,6 +410,7 @@ static void reachabilityHandler(
}
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
LOGD(@"Postflight starting");
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
@@ -482,6 +498,11 @@ static void reachabilityHandler(
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
syncState.compressedContentEncoding =
config.enableBackwardsCompatibleContentEncoding ? @"zlib" : @"deflate";
syncState.FCMToken = self.FCMToken;
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return syncState;
}

View File

@@ -39,6 +39,7 @@
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kPrimaryUser] = self.syncState.machineOwner;
if (self.syncState.FCMToken) requestDict[kFCMToken] = self.syncState.FCMToken;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
@@ -102,20 +103,19 @@
}];
self.syncState.eventBatchSize = [resp[kBatchSize] unsignedIntegerValue] ?: kDefaultEventBatchSize;
self.syncState.FCMToken = resp[kFCMToken];
// Don't let these go too low
NSUInteger FCMIntervalValue = [resp[kFCMFullSyncInterval] unsignedIntegerValue];
self.syncState.FCMFullSyncInterval =
(FCMIntervalValue < kDefaultFullSyncInterval) ? kDefaultFCMFullSyncInterval : FCMIntervalValue;
self.syncState.FCMFullSyncInterval = (FCMIntervalValue < kDefaultFullSyncInterval)
? kDefaultFCMFullSyncInterval
: FCMIntervalValue;
FCMIntervalValue = [resp[kFCMGlobalRuleSyncDeadline] unsignedIntegerValue];
self.syncState.FCMGlobalRuleSyncDeadline =
(FCMIntervalValue < 60) ? kDefaultFCMGlobalRuleSyncDeadline : FCMIntervalValue;
(FCMIntervalValue < 60) ? kDefaultFCMGlobalRuleSyncDeadline : FCMIntervalValue;
// Check if our sync interval has changed
NSUInteger intervalValue = [resp[kFullSyncInterval] unsignedIntegerValue];
self.syncState.fullSyncInterval =
(intervalValue < 60) ? kDefaultFullSyncInterval : intervalValue;
self.syncState.fullSyncInterval = (intervalValue < 60) ? kDefaultFullSyncInterval : intervalValue;
if ([resp[kClientMode] isEqual:kClientModeMonitor]) {
self.syncState.clientMode = SNTClientModeMonitor;

View File

@@ -70,7 +70,7 @@
NSData *compressed = [requestBody zlibCompressed];
if (compressed) {
requestBody = compressed;
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
[req setValue:self.syncState.compressedContentEncoding forHTTPHeaderField:@"Content-Encoding"];
}
[req setHTTPBody:requestBody];
@@ -83,20 +83,33 @@
- (NSDictionary *)performRequest:(NSURLRequest *)request timeout:(NSTimeInterval)timeout {
NSHTTPURLResponse *response;
NSError *error;
NSData *data = [self performRequest:request timeout:timeout response:&response error:&error];
NSData *data;
// If the original request failed, attempt to get a new XSRF token and try again.
// Unfortunately some servers cause NSURLSession to return 'client cert required' or
// 'could not parse response' when a 403 occurs and SSL cert auth is enabled.
if ((response.statusCode == 403 ||
error.code == NSURLErrorClientCertificateRequired ||
error.code == NSURLErrorCannotParseResponse) &&
[self fetchXSRFToken]) {
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest setValue:self.syncState.xsrfToken forHTTPHeaderField:kXSRFToken];
return [self performRequest:mutableRequest timeout:timeout];
for (int attempt = 1; attempt < 6; ++attempt) {
if (attempt > 1) {
struct timespec ts = {.tv_sec = (attempt * 2)};
nanosleep(&ts, NULL);
}
LOGD(@"Performing request, attempt %d", attempt);
data = [self performRequest:request timeout:timeout response:&response error:&error];
if (response.statusCode == 200) break;
// If the original request failed because of an auth error, attempt to get a new XSRF token and
// try again. Unfortunately some servers cause NSURLSession to return 'client cert required' or
// 'could not parse response' when a 403 occurs and SSL cert auth is enabled.
if ((response.statusCode == 403 ||
error.code == NSURLErrorClientCertificateRequired ||
error.code == NSURLErrorCannotParseResponse) &&
[self fetchXSRFToken]) {
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest setValue:self.syncState.xsrfToken forHTTPHeaderField:kXSRFToken];
request = mutableRequest;
continue;
}
}
// If the final attempt resulted in an error, log the error and return nil.
if (response.statusCode != 200) {
long code;
NSString *errStr;

View File

@@ -78,4 +78,8 @@
/// Reference to the serial operation queue used for accessing allowlistNotifications.
@property(weak) NSOperationQueue *allowlistNotificationQueue;
/// The header value for ContentEncoding when sending compressed content.
/// Either "deflate" (default) or "zlib".
@property(copy) NSString *compressedContentEncoding;
@end

View File

@@ -16,6 +16,8 @@ objc_library(
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTEventProvider.h",
"Logs/SNTEventLog.h",
"Logs/SNTEventLog.m",
@@ -50,6 +52,7 @@ objc_library(
"IOKit",
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
@@ -77,6 +80,10 @@ macos_bundle(
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Daemon_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santad_lib"],
@@ -115,6 +122,7 @@ santa_unit_test(
"bsm",
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",

View File

@@ -0,0 +1,20 @@
/// Copyright 2021 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>
#include "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
@interface SNTCachingEndpointSecurityManager : SNTEndpointSecurityManager
@end

View File

@@ -0,0 +1,200 @@
/// Copyright 2021 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 "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
#import "Source/common/SantaCache.h"
#import "Source/common/SNTLogging.h"
#include <bsm/libbsm.h>
#include <EndpointSecurity/EndpointSecurity.h>
uint64_t GetCurrentUptime() {
return clock_gettime_nsec_np(CLOCK_MONOTONIC);
}
template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t) {
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
}
@implementation SNTCachingEndpointSecurityManager {
SantaCache<santa_vnode_id_t, uint64_t> *_decisionCache;
}
- (instancetype)init {
self = [super init];
if (self) {
// TODO(rah): Consider splitting into root/non-root cache
_decisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
}
return self;
}
- (void)dealloc {
if (_decisionCache) delete _decisionCache;
}
- (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
auto vnode_id = [self vnodeIDForFile:m->event.exec.target->executable];
while (true) {
// Check to see if item is in cache
auto return_action = [self checkCache:vnode_id];
// If item was in cache with a valid response, return it.
// If item is in cache but hasn't received a response yet, sleep for a bit.
// If item is not in cache, break out of loop and forward request to callback.
if (RESPONSE_VALID(return_action)) {
switch (return_action) {
case ACTION_RESPOND_ALLOW:
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
break;
case ACTION_RESPOND_ALLOW_COMPILER: {
pid_t pid = audit_token_to_pid(m->process->audit_token);
[self setIsCompilerPID:pid];
// Don't let ES cache compilers
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
break;
}
default:
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
break;
}
return YES;
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
// TODO(rah): Look at a replacement for msleep(), maybe NSCondition
usleep(5000);
} else {
break;
}
}
[self addToCache:vnode_id decision:ACTION_REQUEST_BINARY currentTicks:0];
return NO;
}
- (int)postAction:(santa_action_t)action forMessage:(santa_message_t)sm
API_AVAILABLE(macos(10.15)) {
es_respond_result_t ret;
switch (action) {
case ACTION_RESPOND_ALLOW_COMPILER:
[self setIsCompilerPID:sm.pid];
// Allow the exec and cache in our internal cache but don't let ES cache, because then
// we won't see future execs of the compiler in order to record the PID.
[self addToCache:sm.vnode_id
decision:ACTION_RESPOND_ALLOW_COMPILER
currentTicks:GetCurrentUptime()];
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
ES_AUTH_RESULT_ALLOW, false);
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE:
[self addToCache:sm.vnode_id decision:ACTION_RESPOND_ALLOW currentTicks:GetCurrentUptime()];
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
ES_AUTH_RESULT_ALLOW, true);
break;
case ACTION_RESPOND_DENY:
[self addToCache:sm.vnode_id decision:ACTION_RESPOND_DENY currentTicks:GetCurrentUptime()];
OS_FALLTHROUGH;
case ACTION_RESPOND_TOOLONG:
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
ES_AUTH_RESULT_DENY, false);
break;
case ACTION_RESPOND_ACK:
return ES_RESPOND_RESULT_SUCCESS;
default:
ret = ES_RESPOND_RESULT_ERR_INVALID_ARGUMENT;
}
return ret;
}
- (void)addToCache:(santa_vnode_id_t)identifier
decision:(santa_action_t)decision
currentTicks:(uint64_t)microsecs {
switch (decision) {
case ACTION_REQUEST_BINARY:
_decisionCache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
break;
case ACTION_RESPOND_ACK:
_decisionCache->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
((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);
if (!_decisionCache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
_decisionCache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
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);
_decisionCache->set(identifier, val, 0);
break;
}
default:
break;
}
// TODO(rah): Look at a replacement for wakeup(), maybe NSCondition
}
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly API_AVAILABLE(macos(10.15)) {
_decisionCache->clear();
if (!self.connectionEstablished) return YES; // if not connected, there's nothing to flush.
return es_clear_cache(self.client) == ES_CLEAR_CACHE_RESULT_SUCCESS;
}
- (NSArray<NSNumber *> *)cacheCounts {
return @[@(_decisionCache->count()), @(0)];
}
- (NSArray<NSNumber *> *)cacheBucketCount {
// TODO: add this, maybe.
return nil;
}
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
uint64_t cache_val = _decisionCache->get(vnodeID);
if (cache_val == 0) return result;
// Decision is stored in upper 8 bits, timestamp in remaining 56.
result = (santa_action_t)(cache_val >> 56);
decision_time = (cache_val & ~(0xFF00000000000000));
if (RESPONSE_VALID(result)) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (500 * 100000); // kMaxCacheDenyTimeMilliseconds
if (expiry_time < GetCurrentUptime()) {
_decisionCache->remove(vnodeID);
return ACTION_UNSET;
}
}
}
return result;
}
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeID {
_decisionCache->remove(vnodeID);
// TODO(rah): Look at a replacement for wakeup(), maybe NSCondition
return 0;
}
@end

View File

@@ -17,5 +17,18 @@
#include "Source/common/SNTKernelCommon.h"
#include "Source/santad/EventProviders/SNTEventProvider.h"
#include <EndpointSecurity/EndpointSecurity.h>
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
const pid_t PID_MAX = 99999;
@interface SNTEndpointSecurityManager : NSObject<SNTEventProvider>
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file;
- (BOOL)isCompilerPID:(pid_t)pid;
- (void)setIsCompilerPID:(pid_t)pid;
- (void)setNotCompilerPID:(pid_t)pid;
@property(readonly, nonatomic) es_client_t *client;
@end

View File

@@ -16,21 +16,19 @@
#include "Source/common/SNTPrefixTree.h"
#import "Source/common/SantaCache.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#include <EndpointSecurity/EndpointSecurity.h>
#include <atomic>
#include <bsm/libbsm.h>
#include <libproc.h>
#include <atomic>
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
#define PID_MAX 99999
@interface SNTEndpointSecurityManager () {
std::atomic<bool> _compilerPIDs[PID_MAX];
}
@interface SNTEndpointSecurityManager ()
@property(nonatomic) es_client_t *client;
@property(nonatomic) SNTPrefixTree *prefixTree;
@property(nonatomic, copy) void (^decisionCallback)(santa_message_t);
@property(nonatomic, copy) void (^logCallback)(santa_message_t);
@@ -40,9 +38,7 @@
@end
@implementation SNTEndpointSecurityManager {
std::atomic<bool> _compilerPIDs[PID_MAX];
}
@implementation SNTEndpointSecurityManager
- (instancetype)init API_AVAILABLE(macos(10.15)) {
self = [super init];
@@ -78,13 +74,16 @@
es_client_t *client = NULL;
es_new_client_result_t ret = es_new_client(&client, ^(es_client_t *c, const es_message_t *m) {
pid_t pid = audit_token_to_pid(m->process->audit_token);
int pidversion = audit_token_to_pidversion(m->process->audit_token);
// If enabled, skip any action generated from another endpoint security client.
if (m->process->is_es_client && config.ignoreOtherEndpointSecurityClients) {
if (m->action_type == ES_ACTION_TYPE_AUTH) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
}
if (self.selfPID != pid) {
LOGD(@"Skipping event type: 0x%x from es_client pid: %d", m->event_type, pid);
}
LOGD(@"Skipping action from es_client pid: %d", pid);
return;
}
@@ -104,8 +103,11 @@
// Ignore unmodified files
if (!m->event.close.modified) return;
// Remove from decision cache in case this is invalidating a cached binary.
[self removeCacheEntryForVnodeID:[self vnodeIDForFile:m->event.close.target]];
// Create a transitive rule if the file was modified by a running compiler
if (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()) {
if ([self isCompilerPID:pid]) {
santa_message_t sm = {};
BOOL truncated = [self populateBufferFromESFile:m->event.close.target
buffer:sm.path
@@ -115,8 +117,12 @@
sm.path, pid);
break;
}
if ([@(sm.path) hasPrefix:@"/dev/"]) {
break;
}
sm.action = ACTION_NOTIFY_WHITELIST;
sm.pid = pid;
sm.pidversion = pidversion;
LOGI(@"CLOSE: creating a transitive rule: path=%s pid=%d", sm.path, sm.pid);
self.decisionCallback(sm);
}
@@ -126,7 +132,7 @@
}
case ES_EVENT_TYPE_NOTIFY_RENAME: {
// Create a transitive rule if the file was renamed by a running compiler
if (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()) {
if ([self isCompilerPID:pid]) {
santa_message_t sm = {};
BOOL truncated = [self populateRenamedNewPathFromESMessage:m->event.rename
buffer:sm.path
@@ -136,8 +142,12 @@
sm.path, pid);
break;
}
if ([@(sm.path) hasPrefix:@"/dev/"]) {
break;
}
sm.action = ACTION_NOTIFY_WHITELIST;
sm.pid = pid;
sm.pidversion = pidversion;
LOGI(@"RENAME: creating a transitive rule: path=%s pid=%d", sm.path, sm.pid);
self.decisionCallback(sm);
}
@@ -147,13 +157,14 @@
}
case ES_EVENT_TYPE_NOTIFY_EXIT: {
// Update the set of running compiler PIDs
if (pid && pid < PID_MAX) self->_compilerPIDs[pid].store(false);
[self setNotCompilerPID:pid];
// Skip the standard pipline and just log.
// Skip the standard pipeline and just log.
if (![config enableForkAndExitLogging]) return;
santa_message_t sm = {};
sm.action = ACTION_NOTIFY_EXIT;
sm.pid = pid;
sm.pidversion = pidversion;
sm.ppid = m->process->original_ppid;
audit_token_t at = m->process->audit_token;
sm.uid = audit_token_to_ruid(at);
@@ -164,13 +175,14 @@
return;
}
case ES_EVENT_TYPE_NOTIFY_FORK: {
// Skip the standard pipline and just log.
// Skip the standard pipeline and just log.
if (![config enableForkAndExitLogging]) return;
santa_message_t sm = {};
sm.action = ACTION_NOTIFY_FORK;
sm.ppid = m->event.fork.child->original_ppid;
audit_token_t at = m->event.fork.child->audit_token;
sm.pid = audit_token_to_pid(at);
sm.pidversion = audit_token_to_pidversion(at);
sm.uid = audit_token_to_ruid(at);
sm.gid = audit_token_to_rgid(at);
dispatch_async(self.esNotifyQueue, ^{
@@ -185,15 +197,12 @@
switch (m->action_type) {
case ES_ACTION_TYPE_AUTH: {
// Create a timer to deny the execution 2 seconds before the deadline,
// Create a timer to deny the execution 5 seconds before the deadline,
// if a response hasn't already been sent. This block will still be enqueued if
// the the deadline - 2 secs is < DISPATCH_TIME_NOW.
// As of 10.15.2, a typical deadline is 60 seconds.
// TODO(bur/rah): Possibly cache decisions made after the deadline. Currently a
// large enough binary will never be allowed to execute. This should be a rare edge case;
// it's probably not worth adding a caching layer just for this.
// the the deadline - 5 secs is < DISPATCH_TIME_NOW.
// As of 10.15.5, a typical deadline is 60 seconds.
auto responded = std::make_shared<std::atomic<bool>>(false);
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -2), self.esAuthQueue, ^(void) {
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -5), self.esAuthQueue, ^(void) {
if (responded->load()) return;
LOGE(@"Deadline reached: deny pid=%d ret=%d",
pid, es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false));
@@ -229,7 +238,7 @@
switch (ret) {
case ES_NEW_CLIENT_RESULT_SUCCESS:
LOGI(@"Connected to EndpointSecurity");
self.client = client;
_client = client;
return;
case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
LOGE(@"Unable to create EndpointSecurity client, not full-disk access permitted");
@@ -244,6 +253,10 @@
}
}
- (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
return NO;
}
- (void)messageHandler:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
santa_message_t sm = {};
sm.es_message = (void *)m;
@@ -254,6 +267,10 @@
switch (m->event_type) {
case ES_EVENT_TYPE_AUTH_EXEC: {
if ([self respondFromCache:m]) {
return;
}
sm.action = ACTION_REQUEST_BINARY;
targetFile = m->event.exec.target->executable;
targetProcess = m->event.exec.target;
@@ -385,6 +402,7 @@
sm.uid = audit_token_to_ruid(targetProcess->audit_token);
sm.gid = audit_token_to_rgid(targetProcess->audit_token);
sm.pid = audit_token_to_pid(targetProcess->audit_token);
sm.pidversion = audit_token_to_pidversion(targetProcess->audit_token);
sm.ppid = targetProcess->original_ppid;
proc_name((m->event_type == ES_EVENT_TYPE_AUTH_EXEC) ? sm.ppid : sm.pid, sm.pname, 1024);
callback(sm);
@@ -395,14 +413,14 @@
self.decisionCallback = callback;
es_event_type_t events[] = {
ES_EVENT_TYPE_AUTH_EXEC,
ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_AUTH_RENAME,
ES_EVENT_TYPE_AUTH_KEXTLOAD,
ES_EVENT_TYPE_AUTH_EXEC,
ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_AUTH_RENAME,
ES_EVENT_TYPE_AUTH_KEXTLOAD,
// This is in the decision callback because it's used for detecting
// the exit of a 'compiler' used by transitive whitelisting.
ES_EVENT_TYPE_NOTIFY_EXIT,
// This is in the decision callback because it's used for detecting
// the exit of a 'compiler' used by transitive whitelisting.
ES_EVENT_TYPE_NOTIFY_EXIT,
};
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
if (sret != ES_RETURN_SUCCESS) LOGE(@"Unable to subscribe to auth events: %d", sret);
@@ -434,12 +452,8 @@
es_respond_result_t ret;
switch (action) {
case ACTION_RESPOND_ALLOW_COMPILER:
if (sm.pid >= PID_MAX) {
LOGE(@"Unable to watch compiler pid=%d >= pid_max=%d", sm.pid, PID_MAX);
} else {
LOGD(@"Watching compiler pid=%d path=%s", sm.pid, sm.path);
self->_compilerPIDs[sm.pid].store(true);
}
[self setIsCompilerPID:sm.pid];
// Allow the exec, but don't cache the decision so subsequent execs of the compiler get
// marked appropriately.
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
@@ -545,4 +559,30 @@
return truncated;
}
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file {
return {
.fsid = (uint64_t)file->stat.st_dev,
.fileid = file->stat.st_ino,
};
}
- (BOOL)isCompilerPID:(pid_t)pid {
return (pid && pid < PID_MAX && self->_compilerPIDs[pid].load());
}
- (void)setIsCompilerPID:(pid_t)pid {
if (pid < 1) {
LOGE(@"Unable to watch compiler pid=%d", pid);
} else if (pid >= PID_MAX) {
LOGE(@"Unable to watch compiler pid=%d >= PID_MAX(%d)", pid, PID_MAX);
} else {
self->_compilerPIDs[pid].store(true);
LOGD(@"Watching compiler pid=%d", pid);
}
}
- (void)setNotCompilerPID:(pid_t)pid {
if (pid && pid < PID_MAX) self->_compilerPIDs[pid].store(false);
}
@end

View File

@@ -65,8 +65,8 @@
char ppath[PATH_MAX] = "(null)";
proc_pidpath(message.pid, ppath, PATH_MAX);
[outStr appendFormat:@"|pid=%d|ppid=%d|process=%s|processpath=%s|uid=%d|user=%@|gid=%d|group=%@",
message.pid, message.ppid, message.pname, ppath,
[outStr appendFormat:@"|pid=%d|pidversion=%d|ppid=%d|process=%s|processpath=%s|uid=%d|user=%@|gid=%d|group=%@",
message.pid, message.pidversion, message.ppid, message.pname, ppath,
message.uid, [self nameForUID:message.uid],
message.gid, [self nameForGID:message.gid]];
@@ -169,8 +169,8 @@
mode = @"U"; break;
}
[outLog appendFormat:@"|pid=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@|mode=%@|path=%@",
message.pid, message.ppid,
[outLog appendFormat:@"|pid=%d|pidversion=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@|mode=%@|path=%@",
message.pid, message.pidversion, message.ppid,
message.uid, [self nameForUID:message.uid],
message.gid, [self nameForGID:message.gid],
mode, [self sanitizeString:@(message.path)]];
@@ -269,15 +269,15 @@
}
- (void)logFork:(santa_message_t)message {
NSString *s = [NSString stringWithFormat:@"action=FORK|pid=%d|ppid=%d|uid=%d|gid=%d",
message.pid, message.ppid, message.uid, message.gid];
NSString *s = [NSString stringWithFormat:@"action=FORK|pid=%d|pidversion=%d|ppid=%d|uid=%d|gid=%d",
message.pid, message.pidversion, message.ppid, message.uid, message.gid];
[self writeLog:s];
}
- (void)logExit:(santa_message_t)message {
NSString *s = [NSString stringWithFormat:@"action=EXIT|pid=%d|ppid=%d|uid=%d|gid=%d",
message.pid, message.ppid, message.uid, message.gid];
NSString *s = [NSString stringWithFormat:@"action=EXIT|pid=%d|pidversion=%d|ppid=%d|uid=%d|gid=%d",
message.pid, message.pidversion, message.ppid, message.uid, message.gid];
[self writeLog:s];
}

View File

@@ -33,6 +33,7 @@
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/EventProviders/SNTDriverManager.h"
#import "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
#import "Source/santad/Logs/SNTFileEventLog.h"
@@ -59,8 +60,13 @@
// Choose an event logger.
// Locate and connect to driver / SystemExtension
if ([configurator enableSystemExtension]) {
LOGI(@"Using EndpointSecurity as event provider.");
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
if ([configurator enableSysxCache]) {
LOGI(@"Using CachingEndpointSecurity as event provider.");
_eventProvider = [[SNTCachingEndpointSecurityManager alloc] init];
} else {
LOGI(@"Using EndpointSecurity as event provider.");
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
}
} else {
LOGI(@"Using Kauth as event provider.");
_eventProvider = [[SNTDriverManager alloc] init];

View File

@@ -85,8 +85,8 @@
LOGE(@"unable to add new transitive rule to database: %@", err.localizedDescription);
} else {
[self.eventLog
writeLog:[NSString stringWithFormat:@"action=ALLOWLIST|pid=%d|path=%s|sha256=%@",
message.pid, target, fi.SHA256]];
writeLog:[NSString stringWithFormat:@"action=ALLOWLIST|pid=%d|pidversion=%d|path=%s|sha256=%@",
message.pid, message.pidversion, target, fi.SHA256]];
}
}
}

View File

@@ -8,18 +8,21 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl",
git_repository(
name = "build_bazel_rules_apple",
remote = "https://github.com/bazelbuild/rules_apple.git",
tag = "0.20.0",
tag = "0.21.2",
)
load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies")
apple_rules_dependencies()
load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies")
apple_support_dependencies()
# Macops MOL* dependencies
git_repository(
name = "MOLAuthenticatingURLSession",
remote = "https://github.com/google/macops-molauthenticatingurlsession.git",
tag = "v2.9",
tag = "v3.0",
)
git_repository(
@@ -34,16 +37,10 @@ git_repository(
tag = "v2.2",
)
git_repository(
name = "MOLFCMClient",
remote = "https://github.com/google/macops-molfcmclient.git",
tag = "v2.0",
)
git_repository(
name = "MOLXPCConnection",
remote = "https://github.com/google/macops-molxpcconnection.git",
tag = "v2.0",
tag = "v2.1",
)
# FMDB
@@ -51,7 +48,7 @@ git_repository(
new_git_repository(
name = "FMDB",
remote = "https://github.com/ccgus/fmdb.git",
tag = "v2.7",
tag = "2.7.7",
build_file_content = """
objc_library(
name = "FMDB",
@@ -69,7 +66,7 @@ objc_library(
new_git_repository(
name = "OCMock",
remote = "https://github.com/erikdoe/ocmock",
tag = "v3.4.3",
tag = "v3.8.1",
build_file_content = """
objc_library(
name = "OCMock",

View File

@@ -175,7 +175,7 @@ Configuration profiles have a `.mobileconfig` file extension. There are many way
| fcm_full_sync_interval* | Integer | The full sync interval if a fcm_token is set. Defaults to 14400 secs (4 hours). |
| fcm_global_rule_sync_deadline* | Integer | The max time to wait before performing a rule sync when a global rule sync FCM message is received. This allows syncing to be staggered for global events to avoid spikes in server load. Defaults to 600 secs (10 min). |
| enable_bundles* | Bool | If set to `True` the bundle scanning feature is enabled. Defaults to `False`. |
| enabled_transitive_rules | Bool | If set to `True` the transitive rule feature is enabled. Defaults to `False`. |
| enable_transitive_rules | Bool | If set to `True` the transitive rule feature is enabled. Defaults to `False`. |
*Held only in memory. Not persistent upon process restart.

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>NotificationSettings</key>
<array>
<dict>
<key>AlertType</key>
<integer>1</integer>
<key>BadgesEnabled</key>
<true/>
<key>BundleIdentifier</key>
<string>com.google.santa</string>
<key>CriticalAlertEnabled</key>
<true/>
<key>NotificationsEnabled</key>
<true/>
<key>ShowInLockScreen</key>
<true/>
<key>ShowInNotificationCenter</key>
<true/>
<key>SoundsEnabled</key>
<false/>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Notifications Payload</string>
<key>PayloadIdentifier</key>
<string>com.google.santa.notificationsettings.F1817DA0-0044-43DD-9540-36EBC60FDA8F</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadType</key>
<string>com.apple.notificationsettings</string>
<key>PayloadUUID</key>
<string>510236AE-D7F8-4131-A4CA-5CC930C51866</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>Configures your Mac to automatically enable Notifications settings for Santa</string>
<key>PayloadDisplayName</key>
<string>Santa Notifications settings</string>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>com.google.santa.notificationsettings.069CA123-6129-46A5-8FD1-49322E5A5755</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>069CA123-6129-46A5-8FD1-49322E5A5755</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -8,8 +8,8 @@ This may be the most complex part of Santa. It does two types of work:
can also inspect individual files. When running without a sync server it
also a supported method of managing the rules database.
The details of santactl's syncing functionality are covered in the syncing.md
document. This document will cover the status work that santactl performs.
The details of santactl's syncing functionality are covered in [introduction/syncing-overview.md](syncing-overview.md).
This document will cover the status work that santactl performs.
##### status
@@ -365,11 +365,11 @@ Recursive lookups of an application or directory is a soon to be added feature
##### rule
The rule command is covered in the [rules.md](rules.md) document.
The rule command is covered in the [Rules](rules.md) document.
##### sync
The sync command is covered in the [syncing.md](syncing.md) document.
The sync command is covered in the [Syncing Overview](syncing-overview.md) document.
##### Debug Commands

View File

@@ -0,0 +1,34 @@
Want to contribute? Great! First, read this page (including the small print at the end).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
Before you start working on a larger contribution, you should get in touch with
us first through the [issue tracker](https://github.com/google/santa/issues)
with your idea so that we can help out and possibly guide you. Co-ordinating
large changes ahead of time can avoid frustration later on.
### Code reviews
All submissions - including submissions by project members - require review. We
use GitHub pull requests for this purpose. GitHub will automatically run the
tests when you mail your pull request and a proper review won't be started until
the tests are complete and passing.
### Code Style
All code submissions should try to match the surrounding code. Wherever possible,
code should adhere to either the
[Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.xml)
or the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).
### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).

View File

@@ -9,7 +9,7 @@ contribute.
The following documents give an overview of how Santa accomplishes binary
authorization at the enterprise scale.
- [Binary Authorization](introduction/binary-authorization.md): How Santa makes allow or deny decisions for any `execve()` taking place.
- [Binary Authorization](introduction/binary-authorization-overview.md): How Santa makes allow or deny decisions for any `execve()` taking place.
- [Syncing](introduction/syncing-overview.md): How configuration and rules are applied from a sync server.
#### Deployment
@@ -19,7 +19,7 @@ authorization at the enterprise scale.
#### Development
* [Building Santa](development/building.md): How to build and load Santa for testing on a development machine.
* [Contributing](../CONTRIBUTING.md): How to contribute a bug fix or new feature to Santa.
* [Contributing](development/contributing.md): How to contribute a bug fix or new feature to Santa.
#### Details
@@ -43,7 +43,6 @@ Additional documentation on the concepts that support the operation of the main
* [events](details/events.md): Represents an `execve()` that was blocked, or would have been blocked, depending on the mode.
* [rules](details/rules.md): Represents allow or deny decisions for a given `execve()`. Can either be a binary's SHA-256 hash or a leaf code-signing certificate's SHA-256 hash.
* [scopes](details/scopes.md): The level at which an `execve()` was allowed or denied from taking place.
* [syncing](introduction/syncing-overview.md): How Santa communicates with a TLS server for configuration, rules and event uploading.
* [ipc](details/ipc.md): How all the components of Santa communicate.
duction/syncing-overview.
* [logs](details/logs.md): What and where Santa logs.

View File

@@ -1,3 +1,3 @@
"""The version for all Santa components."""
SANTA_VERSION = "1.15"
SANTA_VERSION = "2021.5"