mirror of
https://github.com/google/santa.git
synced 2026-01-15 09:17:59 -05:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1523d58429 | ||
|
|
81049db170 | ||
|
|
c110245701 | ||
|
|
d7a56b9bd4 | ||
|
|
4bb5804a6f | ||
|
|
e68fb7235a | ||
|
|
f93e7ef879 | ||
|
|
f472f4821c | ||
|
|
1c97761038 | ||
|
|
e569a684b7 | ||
|
|
66c32dc526 | ||
|
|
075d3cbc11 | ||
|
|
340326df8a | ||
|
|
f52edd2a76 | ||
|
|
11c247e33a | ||
|
|
a859b9b341 | ||
|
|
c190f1f52d | ||
|
|
87dc191494 | ||
|
|
3a19591822 | ||
|
|
b225c0740e | ||
|
|
d1fffb4636 | ||
|
|
9d7ca62e46 | ||
|
|
2a6073a9a1 | ||
|
|
296f06582b | ||
|
|
0e27dab4c6 | ||
|
|
256836d7f8 | ||
|
|
b117d8106e | ||
|
|
c980223215 | ||
|
|
635b33ebf9 | ||
|
|
b6f35c9b9f | ||
|
|
796109cc60 | ||
|
|
38f580de72 | ||
|
|
c7a58c77e7 | ||
|
|
9a4fe782d7 | ||
|
|
fbb5f3728f | ||
|
|
24b96c4798 | ||
|
|
1edf6d9200 | ||
|
|
ac1f8ea1b8 | ||
|
|
9923f601b6 | ||
|
|
471ae89406 | ||
|
|
54d6653973 | ||
|
|
27ee66597b | ||
|
|
10f2d852f5 | ||
|
|
1fcb63dc92 | ||
|
|
7944f681f8 | ||
|
|
e3aedc92ba | ||
|
|
d2b6c2b6c2 | ||
|
|
d026989dfb | ||
|
|
e7a8e9b6ac | ||
|
|
1d9af01353 | ||
|
|
9c6af7fc03 | ||
|
|
543b1a29fe | ||
|
|
625ec67789 | ||
|
|
c5696d71e7 | ||
|
|
5f3cef52de | ||
|
|
eeed0b5aa6 | ||
|
|
9ef171e663 | ||
|
|
ad1868a50f | ||
|
|
78643d3c49 | ||
|
|
8b22c85a64 | ||
|
|
58fe5d3d76 | ||
|
|
8b2227967e | ||
|
|
65693acea1 | ||
|
|
7cea383930 | ||
|
|
5ae2376158 | ||
|
|
e851337eac | ||
|
|
2e53834980 | ||
|
|
aef139e93c | ||
|
|
a9e5bf09a7 | ||
|
|
4ee3f281c3 | ||
|
|
462ce89d42 | ||
|
|
44117833c0 | ||
|
|
8b6e029da2 | ||
|
|
f183e246df | ||
|
|
c60a35f280 | ||
|
|
4f65965277 | ||
|
|
01e4e15b81 | ||
|
|
532cb37e0b | ||
|
|
9d379d3884 | ||
|
|
3e7a191bf7 | ||
|
|
c5a048f4d9 | ||
|
|
f4769bad90 | ||
|
|
254497ad15 | ||
|
|
0a83445838 | ||
|
|
eff287259e | ||
|
|
6f2c0e3457 | ||
|
|
38769f7cd1 | ||
|
|
fa785ad3c2 | ||
|
|
5dae0cabdd | ||
|
|
a8b4f4ea7e | ||
|
|
2221c93bbc | ||
|
|
d1c33baf35 | ||
|
|
d2bbdff373 | ||
|
|
db1d65f944 | ||
|
|
d17aeac2f4 | ||
|
|
7840270dd0 | ||
|
|
dcf44c9872 | ||
|
|
fc365c888f | ||
|
|
85f0782399 | ||
|
|
64bc34c302 | ||
|
|
e2fc4c735d |
5
.bazelrc
5
.bazelrc
@@ -1,2 +1,5 @@
|
||||
build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
build --host_force_python=PY2
|
||||
|
||||
build --copt=-Werror
|
||||
build --copt=-Wall
|
||||
build --copt=-Wno-error=deprecated-declarations
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
Language: ObjC
|
||||
BasedOnStyle: Google
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
# Disable ColumnLimit because it causes some very weird line breaks.
|
||||
# For ObjC the limit is 100
|
||||
# For Cpp the limit is 80
|
||||
ColumnLimit: 0
|
||||
IndentWidth: 2
|
||||
ObjCBlockIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
|
||||
# For ObjC, the line limit is 100
|
||||
ColumnLimit: 100
|
||||
|
||||
# Allow short case statements to be on a single line
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
|
||||
# Ban short loops and functions on a single line
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
|
||||
# Allow spaces in NSArray/NSDictionary literals @[ and @{
|
||||
SpacesInContainerLiterals: true
|
||||
@@ -20,3 +20,13 @@ SpacesInContainerLiterals: true
|
||||
# For pointers, always put the * next to the variable name.
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
|
||||
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
BasedOnStyle: Google
|
||||
|
||||
# For C++, the line limit is 80
|
||||
ColumnLimit: 80
|
||||
|
||||
96
.github/workflows/ci.yml
vendored
Normal file
96
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
preqs:
|
||||
runs-on: macos-latest
|
||||
outputs:
|
||||
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
|
||||
build_driver: ${{ steps.step1.outputs.build_driver }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Check If We Need to Run Build/Test
|
||||
id: step1
|
||||
run: |
|
||||
git remote add mainline https://github.com/google/santa.git
|
||||
git fetch mainline main
|
||||
git diff --name-only mainline/main HEAD > files.txt
|
||||
echo "FILES CHANGED: $(wc -l ./files.txt)\n"
|
||||
|
||||
cat files.txt
|
||||
|
||||
build_driver=0
|
||||
build_and_run_tests=0
|
||||
|
||||
for file in `cat files.txt`; do
|
||||
if [[ $file = Source/* ]]; then
|
||||
build_and_run_test=1;
|
||||
if [[ $file = Source/santa_driver/* ]]; then
|
||||
build_driver=1;
|
||||
break;
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $build_and_run_test != 0 ]]; then
|
||||
echo "NEED TO RUN BUILD AND TESTS"
|
||||
echo "::set-output name=run_build_and_tests::true"
|
||||
else
|
||||
echo "::set-output name=run_build_and_tests::false"
|
||||
fi
|
||||
|
||||
if [[ $build_driver != 0 ]]; then
|
||||
echo "NEED TO BUILD DRIVER"
|
||||
echo "::set-output name=build_driver::true"
|
||||
else
|
||||
echo "::set-output name=build_driver::false"
|
||||
fi
|
||||
|
||||
build_userspace:
|
||||
runs-on: macos-latest
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
|
||||
|
||||
build_driver:
|
||||
runs-on: macos-latest
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.build_driver == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Driver
|
||||
run: bazel build --apple_generate_dsym -c opt :release_driver --define=SANTA_BUILD_TYPE=ci
|
||||
|
||||
unit_tests:
|
||||
runs-on: macos-latest
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run All Tests
|
||||
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci
|
||||
|
||||
test_coverage:
|
||||
runs-on: macos-latest
|
||||
needs: [preqs]
|
||||
if: needs.preqs.outputs.run_build_and_tests == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Generate test coverage
|
||||
run: sh ./generate_cov.sh
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ./CoverageData/info.lcov
|
||||
flag-name: Unit
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,9 +1,16 @@
|
||||
.DS_Store
|
||||
default.profraw
|
||||
*.profraw
|
||||
*.provisionprofile
|
||||
bazel-*
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
Santa.xcodeproj/project.xcworkspace
|
||||
Santa.xcworkspace/xcuserdata
|
||||
Santa.xcworkspace/xcshareddata
|
||||
Santa.xcodeproj/*
|
||||
Santa.xcworkspace/*
|
||||
CoverageData/*
|
||||
*.tulsiconf-user
|
||||
xcuserdata
|
||||
tulsigen-*
|
||||
*.crt
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.keychain
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -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
|
||||
80
BUILD
80
BUILD
@@ -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,16 +171,67 @@ 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:SNTApplicationTest",
|
||||
"//Source/santad:SNTEventTableTest",
|
||||
"//Source/santad:SNTExecutionControllerTest",
|
||||
"//Source/santad:SNTRuleTableTest",
|
||||
"//Source/santad:SNTEndpointSecurityManagerTest",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
1
CONTRIBUTING.md
Symbolic link
@@ -0,0 +1 @@
|
||||
docs/development/contributing.md
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
#include <SantaCache.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data,
|
||||
std::size_t size) {
|
||||
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
|
||||
|
||||
std::uint64_t fields[2] = {};
|
||||
@@ -33,7 +34,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
auto returned_value = decision_cache.get(fields[0]);
|
||||
|
||||
if (returned_value != fields[1]) {
|
||||
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value << "\n";
|
||||
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value
|
||||
<< "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <SNTCommandSyncConstants.h>
|
||||
#include <SNTCommandSyncRuleDownload.h>
|
||||
#include <SNTCommandSyncState.h>
|
||||
#include <SNTCommandSyncConstants.h>
|
||||
#include <SNTRule.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
@@ -57,6 +57,6 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@@ -23,15 +23,14 @@
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > 16) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= 16)" << std::endl;
|
||||
std::cerr << "Invalid buffer size of " << size << " (should be <= 16)" << std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
santa_vnode_id_t vnodeID = {};
|
||||
std::memcpy(&vnodeID, data, size);
|
||||
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
@@ -40,16 +39,20 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;;
|
||||
}
|
||||
}];
|
||||
|
||||
[[daemonConn remoteObjectProxy]
|
||||
checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;
|
||||
;
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@@ -34,9 +34,8 @@ struct InputData {
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > sizeof(InputData)) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= " << sizeof(InputData)
|
||||
<< ")" << std::endl;
|
||||
std::cerr << "Invalid buffer size of " << size << " (should be <= " << sizeof(InputData) << ")"
|
||||
<< std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -45,11 +44,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
std::memcpy(&input_data, data, size);
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.state = (SNTRuleState) input_data.state;
|
||||
newRule.type = (SNTRuleType) input_data.type;
|
||||
newRule.state = (SNTRuleState)input_data.state;
|
||||
newRule.type = (SNTRuleType)input_data.type;
|
||||
newRule.shasum = @(input_data.hash);
|
||||
newRule.customMsg = @"";
|
||||
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
@@ -57,17 +56,18 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[[daemonConn remoteObjectProxy]
|
||||
databaseRuleAddRules:@[ newRule ]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
22
Podfile
22
Podfile
@@ -1,22 +0,0 @@
|
||||
|
||||
def common_pods
|
||||
pod 'MOLXPCConnection'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'FMDB'
|
||||
pod 'MOLCertificate'
|
||||
pod 'OCMock'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLFCMClient'
|
||||
end
|
||||
|
||||
project './Santa.xcodeproj'
|
||||
|
||||
project = Xcodeproj::Project.open "./Santa.xcodeproj"
|
||||
project.targets.each do |t|
|
||||
if t.name == "santa-driver"
|
||||
next
|
||||
end
|
||||
target t.name do
|
||||
common_pods
|
||||
end
|
||||
end
|
||||
46
Podfile.lock
46
Podfile.lock
@@ -1,46 +0,0 @@
|
||||
PODS:
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- MOLAuthenticatingURLSession (2.4):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLCertificate (1.9)
|
||||
- MOLCodesignChecker (1.10):
|
||||
- MOLCertificate (~> 1.8)
|
||||
- MOLFCMClient (1.8):
|
||||
- MOLAuthenticatingURLSession (~> 2.4)
|
||||
- MOLXPCConnection (1.2):
|
||||
- MOLCodesignChecker (~> 1.9)
|
||||
- OCMock (3.5)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- MOLFCMClient
|
||||
- MOLXPCConnection
|
||||
- OCMock
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- MOLFCMClient
|
||||
- MOLXPCConnection
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
|
||||
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
|
||||
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
|
||||
MOLFCMClient: 2bfbacd45cc11e1ca3c077e97b80401c4e4a54f1
|
||||
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
|
||||
OCMock: 4ab4577fc941af31f4a0398f6e7e230cf21fc72a
|
||||
|
||||
PODFILE CHECKSUM: d03767a9915896232523962c98d9ff7294aec2b7
|
||||
|
||||
COCOAPODS: 1.7.5
|
||||
21
README.md
21
README.md
@@ -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 [](https://github.com/google/santa/actions/workflows/ci.yml) [](https://coveralls.io/github/google/santa?branch=main)
|
||||
|
||||
<p align="center">
|
||||
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
@@ -23,8 +18,8 @@ Santa is a project of Google's Macintosh Operations Team.
|
||||
# Docs
|
||||
|
||||
The Santa docs are stored in the
|
||||
[Docs](https://github.com/google/santa/blob/master/docs) directory. A Read the
|
||||
Docs instance is available here: https://santa.readthedocs.io.
|
||||
[Docs](https://github.com/google/santa/blob/master/docs) directory. The docs are
|
||||
published at http://santa.dev.
|
||||
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
instructions for developing Santa itself.
|
||||
@@ -145,6 +140,12 @@ 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.
|
||||
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
|
||||
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
|
||||
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
|
||||
|
||||
* Alternatively, `santactl` can configure rules locally (without a sync
|
||||
server).
|
||||
@@ -154,8 +155,8 @@ protect hosts in whatever other ways you see fit.
|
||||
A tool like Santa doesn't really lend itself to screenshots, so here's a video
|
||||
instead.
|
||||
|
||||
<p align="center"> <img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif"
|
||||
alt="Santa Block Video" /> </p>
|
||||
|
||||
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
|
||||
|
||||
# Kext Signing
|
||||
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,79 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
debugAsWhichUser = "root"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
10
Santa.xcworkspace/contents.xcworkspacedata
generated
10
Santa.xcworkspace/contents.xcworkspacedata
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Santa.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,8 +1,23 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
licenses(["notice"])
|
||||
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SantaCacheTest",
|
||||
srcs = [
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage",
|
||||
@@ -10,6 +25,7 @@ objc_library(
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
@@ -21,6 +37,7 @@ objc_library(
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
@@ -35,7 +52,7 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
)
|
||||
@@ -46,7 +63,6 @@ objc_library(
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTLogging",
|
||||
":SNTStrengthify",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
@@ -76,12 +92,18 @@ 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(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
hdrs = ["SNTLogging.h"],
|
||||
deps = [":SNTConfigurator"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
@@ -144,6 +166,15 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCMetricServiceInterface",
|
||||
srcs = ["SNTXPCMetricServiceInterface.m"],
|
||||
hdrs = ["SNTXPCMetricServiceInterface.h"],
|
||||
deps = [
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCControlInterface",
|
||||
srcs = ["SNTXPCControlInterface.m"],
|
||||
@@ -166,6 +197,12 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTMetricSet",
|
||||
srcs = ["SNTMetricSet.m"],
|
||||
hdrs = ["SNTMetricSet.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncdInterface",
|
||||
srcs = ["SNTXPCSyncdInterface.m"],
|
||||
@@ -207,6 +244,7 @@ santa_unit_test(
|
||||
resources = [
|
||||
"testdata/bad_pagezero",
|
||||
"testdata/missing_pagezero",
|
||||
"testdata/32bitplist",
|
||||
],
|
||||
structured_resources = glob([
|
||||
"testdata/BundleExample.app/**",
|
||||
@@ -218,5 +256,11 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTPrefixTreeTest",
|
||||
srcs = ["SNTPrefixTreeTest.mm"],
|
||||
deps = ["SNTPrefixTree"],
|
||||
deps = [":SNTPrefixTree"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTMetricSetTest",
|
||||
srcs = ["SNTMetricSetTest.m"],
|
||||
deps = [":SNTMetricSet"],
|
||||
)
|
||||
|
||||
@@ -17,31 +17,33 @@
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *htmlHeader = @"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: %@;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
NSString *htmlHeader =
|
||||
@"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: %@;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
|
||||
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
|
||||
@"@media (prefers-color-scheme: dark) {"
|
||||
@" body {"
|
||||
@" color: #ddd;"
|
||||
@" }"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
|
||||
@"@media (prefers-color-scheme: dark) {"
|
||||
@" body {"
|
||||
@" color: #ddd;"
|
||||
@" }"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
|
||||
// Support Dark Mode. Note, the returned NSAttributedString is static and does not update when
|
||||
// the OS switches modes.
|
||||
NSString *mode = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"];
|
||||
BOOL dark = [mode isEqualToString:@"Dark"];
|
||||
BOOL dark = [mode isEqualToString:@"Dark"];
|
||||
htmlHeader = [NSString stringWithFormat:htmlHeader, dark ? @"#ddd" : @"#333"];
|
||||
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
@@ -89,13 +91,14 @@
|
||||
|
||||
// Strip any HTML tags out of the message. Also remove any content inside <style> tags and
|
||||
// replace <br> elements with a newline.
|
||||
NSString *stripXslt = @"<?xml version='1.0' encoding='utf-8'?>"
|
||||
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
|
||||
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
|
||||
@"<xsl:output method='text'/>"
|
||||
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
|
||||
@"<xsl:template match='style'/>"
|
||||
@"</xsl:stylesheet>";
|
||||
NSString *stripXslt =
|
||||
@"<?xml version='1.0' encoding='utf-8'?>"
|
||||
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
|
||||
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
|
||||
@"<xsl:output method='text'/>"
|
||||
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
|
||||
@"<xsl:template match='style'/>"
|
||||
@"</xsl:stylesheet>";
|
||||
NSData *data = [xml objectByApplyingXSLTString:stripXslt arguments:NULL error:&error];
|
||||
if (error || ![data isKindOfClass:[NSData class]]) {
|
||||
return html;
|
||||
@@ -106,13 +109,16 @@
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *hostname = [SNTSystemInfo longHostname];
|
||||
NSString *uuid = [SNTSystemInfo hardwareUUID];
|
||||
NSString *serial = [SNTSystemInfo serialNumber];
|
||||
NSString *formatStr = config.eventDetailURL;
|
||||
if (!formatStr.length) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
@@ -122,6 +128,15 @@
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
if (hostname.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%hostname%" withString:hostname];
|
||||
}
|
||||
if (uuid.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%uuid%" withString:uuid];
|
||||
}
|
||||
if (serial.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%" withString:serial];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
}
|
||||
|
||||
@@ -91,7 +91,15 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeFilelog,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
SNTMetricFormatTypeUnknown,
|
||||
SNTMetricFormatTypeRawJSON,
|
||||
SNTMetricFormatTypeJSON,
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath = "/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
static const char *kSantaDPath =
|
||||
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
|
||||
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
static const char *kSantaCtlPath = "/Applications/Santa.app/Contents/MacOS/santactl";
|
||||
static const char *kSantaAppPath = "/Applications/Santa.app";
|
||||
|
||||
@@ -168,11 +168,21 @@
|
||||
/// Use the bundled SystemExtension on macOS 10.15+, defaults to YES.
|
||||
/// Disable to continue using the bundled KEXT.
|
||||
/// This is a one way switch, if this is ever true on macOS 10.15+ the KEXT will be deleted.
|
||||
/// This gives admins control over the timing of switching to the SystemExtension. The intended use case is to have an MDM deliver
|
||||
/// the requisite SystemExtension and TCC profiles before attempting to load.
|
||||
/// This gives admins control over the timing of switching to the SystemExtension. The intended use
|
||||
/// case is to have an MDM deliver the requisite SystemExtension and TCC profiles before attempting
|
||||
/// to load.
|
||||
///
|
||||
@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
|
||||
|
||||
///
|
||||
@@ -191,6 +201,9 @@
|
||||
/// %file_sha% -- SHA-256 of the file that was blocked.
|
||||
/// %machine_id% -- ID of the machine.
|
||||
/// %username% -- executing user.
|
||||
/// %serial% -- System's serial number.
|
||||
/// %uuid% -- System's UUID.
|
||||
/// %hostname% -- System's full hostname.
|
||||
///
|
||||
/// @note: This is not an NSURL because the format-string parsing is done elsewhere.
|
||||
///
|
||||
@@ -317,6 +330,63 @@
|
||||
///
|
||||
@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.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL ignoreOtherEndpointSecurityClients;
|
||||
|
||||
///
|
||||
/// If true, debug logging will be enabled for all Santa components. Defaults to false.
|
||||
/// Passing --debug as an executable argument will enable debug logging for that specific
|
||||
/// component.
|
||||
///
|
||||
@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;
|
||||
|
||||
///
|
||||
/// True if metricsFormat and metricsURL are set. False otherwise.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL exportMetrics;
|
||||
|
||||
///
|
||||
/// Format to export Metrics as.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTMetricFormatType metricFormat;
|
||||
|
||||
///
|
||||
/// URL describing where metrics are exported, defaults to nil.
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *metricURL;
|
||||
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@@ -24,13 +23,16 @@
|
||||
/// A NSUserDefaults object set to use the com.google.santa suite.
|
||||
@property(readonly, nonatomic) NSUserDefaults *defaults;
|
||||
|
||||
// Keys and expected value types.
|
||||
/// Keys and expected value types.
|
||||
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
|
||||
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
|
||||
|
||||
/// Holds the configurations from a sync server and mobileconfig.
|
||||
@property NSMutableDictionary *syncState;
|
||||
@property NSMutableDictionary *configState;
|
||||
|
||||
/// Was --debug passed as an argument to this process?
|
||||
@property(readonly, nonatomic) BOOL debugFlag;
|
||||
@end
|
||||
|
||||
@implementation SNTConfigurator
|
||||
@@ -77,8 +79,18 @@ 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";
|
||||
@@ -89,6 +101,10 @@ static NSString *const kAllowedPathRegexKeyDeprecated = @"WhitelistRegex";
|
||||
static NSString *const kBlockedPathRegexKey = @"BlockedPathRegex";
|
||||
static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
|
||||
|
||||
// TODO(markowsky): move these to sync server only.
|
||||
static NSString *const kMetricFormat = @"MetricFormat";
|
||||
static NSString *const kMetricURL = @"MetricURL";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
|
||||
@@ -126,7 +142,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kBlockedPathRegexKey : re,
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey: number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
@@ -139,7 +155,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
kMachineIDKey : string,
|
||||
@@ -151,12 +167,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kEventLogPath : string,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSystemExtension : number,
|
||||
kEnableSysxCache : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
kEnableDebugLogging : number,
|
||||
kEnableBackwardsCompatibleContentEncoding : number,
|
||||
kFCMProject : string,
|
||||
kFCMEntity : string,
|
||||
kFCMAPIKey : string,
|
||||
kMetricFormat : string,
|
||||
kMetricURL : string,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
_configState = [self readForcedConfig];
|
||||
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
|
||||
_debugFlag = [[NSProcessInfo processInfo].arguments containsObject:@"--debug"];
|
||||
[self startWatchingDefaults];
|
||||
}
|
||||
return self;
|
||||
@@ -322,10 +348,42 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableForkAndExitLogging {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingIgnoreOtherEndpointSecurityClients {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableDebugLogging {
|
||||
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 {
|
||||
@@ -345,8 +403,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
|
||||
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
|
||||
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
|
||||
} else {
|
||||
LOGW(@"Ignoring request to change client mode to %ld", newMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +465,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
NSArray *filters = self.configState[kFileChangesPrefixFiltersKey];
|
||||
for (id filter in filters) {
|
||||
if (![filter isKindOfClass:[NSString class]]) {
|
||||
LOGE(@"Ignoring FileChangesPrefixFilters: array contains a non-string %@", filter);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
@@ -420,7 +475,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
NSString *urlString = self.configState[kSyncBaseURLKey];
|
||||
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
if (urlString && !url) LOGW(@"SyncBaseURL is not a valid URL!");
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -565,11 +619,73 @@ 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;
|
||||
}
|
||||
|
||||
- (BOOL)ignoreOtherEndpointSecurityClients {
|
||||
NSNumber *number = self.configState[kIgnoreOtherEndpointSecurityClients];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableDebugLogging {
|
||||
NSNumber *number = self.configState[kEnableDebugLogging];
|
||||
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);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns YES if all of the necessary options are set to export metrics, NO
|
||||
/// otherwise.
|
||||
///
|
||||
- (BOOL)exportMetrics {
|
||||
return [self metricFormat] != SNTMetricFormatTypeUnknown &&
|
||||
![self.configState[kMetricURL] isEqualToString:@""];
|
||||
}
|
||||
|
||||
- (SNTMetricFormatType)metricFormat {
|
||||
NSString *normalized = [self.configState[kMetricFormat] lowercaseString];
|
||||
normalized = [normalized stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
if ([normalized isEqualToString:@"rawjson"]) {
|
||||
return SNTMetricFormatTypeRawJSON;
|
||||
} else if ([normalized isEqualToString:@"json"]) {
|
||||
return SNTMetricFormatTypeJSON;
|
||||
} else {
|
||||
return SNTMetricFormatTypeUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURL *)metricURL {
|
||||
return [NSURL URLWithString:self.configState[kMetricURL]];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
///
|
||||
@@ -593,7 +709,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
// Only santad should read this file.
|
||||
if (geteuid() != 0) return nil;
|
||||
NSMutableDictionary *syncState =
|
||||
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
|
||||
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
|
||||
for (NSString *key in syncState.allKeys) {
|
||||
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
|
||||
@@ -619,8 +735,9 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
syncState[kAllowedPathRegexKey] = [syncState[kAllowedPathRegexKey] pattern];
|
||||
syncState[kBlockedPathRegexKey] = [syncState[kBlockedPathRegexKey] pattern];
|
||||
[syncState writeToFile:kSyncStateFilePath atomically:YES];
|
||||
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
|
||||
ofItemAtPath:kSyncStateFilePath error:NULL];
|
||||
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @0644}
|
||||
ofItemAtPath:kSyncStateFilePath
|
||||
error:NULL];
|
||||
}
|
||||
|
||||
- (void)clearSyncState {
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
|
||||
///
|
||||
/// Initializer for already resolved paths.
|
||||
///
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <fmdb/FMDB.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#import <fmdb/FMDB.h>
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
|
||||
// Simple class to hold the data of a mach_header and the offset within the file
|
||||
// in which that header was found.
|
||||
@interface MachHeaderWithOffset : NSObject
|
||||
@@ -181,30 +180,27 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1_Final(digest, &c1);
|
||||
NSString *const SHA1FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
*sha1 = [[NSString alloc]
|
||||
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19]];
|
||||
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
|
||||
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
|
||||
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19]];
|
||||
}
|
||||
if (sha256) {
|
||||
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256_Final(digest, &c256);
|
||||
NSString *const SHA256FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
|
||||
*sha256 = [[NSString alloc]
|
||||
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
|
||||
digest[3], digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11], digest[12],
|
||||
digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19], digest[20],
|
||||
digest[21], digest[22], digest[23], digest[24],
|
||||
digest[25], digest[26], digest[27], digest[28],
|
||||
digest[29], digest[30], digest[31]];
|
||||
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
|
||||
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
|
||||
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
|
||||
digest[17], digest[18], digest[19], digest[20], digest[21], digest[22],
|
||||
digest[23], digest[24], digest[25], digest[26], digest[27], digest[28],
|
||||
digest[29], digest[30], digest[31]];
|
||||
}
|
||||
} @finally {
|
||||
free(chunk);
|
||||
@@ -292,15 +288,15 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
- (BOOL)isMissingPageZero {
|
||||
// This method only checks i386 arch because the kernel enforces this for other archs
|
||||
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
|
||||
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86
|
||||
cpuSubType:CPU_SUBTYPE_I386_ALL]];
|
||||
MachHeaderWithOffset *x86Header =
|
||||
self.machHeaders[[self nameForCPUType:CPU_TYPE_X86 cpuSubType:CPU_SUBTYPE_I386_ALL]];
|
||||
if (!x86Header) return NO;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
|
||||
if (mh->filetype != MH_EXECUTE) return NO;
|
||||
|
||||
NSRange range = NSMakeRange(x86Header.offset + sizeof(struct mach_header),
|
||||
sizeof(struct segment_command));
|
||||
NSRange range =
|
||||
NSMakeRange(x86Header.offset + sizeof(struct mach_header), sizeof(struct segment_command));
|
||||
NSData *lcData = [self safeSubdataWithRange:range];
|
||||
if (!lcData) return NO;
|
||||
|
||||
@@ -310,9 +306,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
struct load_command *lc = (struct load_command *)[lcData bytes];
|
||||
if (lc->cmd == LC_SEGMENT) {
|
||||
struct segment_command *segment = (struct segment_command *)lc;
|
||||
if (segment->vmaddr == 0 && segment->vmsize != 0 &&
|
||||
segment->initprot == 0 && segment->maxprot == 0 &&
|
||||
strcmp("__PAGEZERO", segment->segname) == 0) {
|
||||
if (segment->vmaddr == 0 && segment->vmsize != 0 && segment->initprot == 0 &&
|
||||
segment->maxprot == 0 && strcmp("__PAGEZERO", segment->segname) == 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
@@ -362,7 +357,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;
|
||||
}
|
||||
@@ -376,7 +371,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
- (NSBundle *)bundle {
|
||||
if (!self.bundleRef) {
|
||||
self.bundleRef =
|
||||
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
|
||||
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
|
||||
}
|
||||
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
|
||||
}
|
||||
@@ -417,8 +412,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
- (NSString *)bundleName {
|
||||
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
|
||||
[[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description]
|
||||
?: [[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleVersion {
|
||||
@@ -467,8 +462,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
|
||||
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0,
|
||||
4096)]];
|
||||
NSData *machHeader =
|
||||
[self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0, 4096)]];
|
||||
if (machHeader) {
|
||||
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
|
||||
@@ -551,24 +546,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
|
||||
@@ -640,9 +662,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
NSURL *dbPath = [NSURL fileURLWithPathComponents:@[
|
||||
fileOwnerHomeDir,
|
||||
@"Library",
|
||||
@"Preferences",
|
||||
fileOwnerHomeDir, @"Library", @"Preferences",
|
||||
@"com.apple.LaunchServices.QuarantineEventsV2"
|
||||
]];
|
||||
FMDatabase *db = [FMDatabase databaseWithPath:[dbPath absoluteString]];
|
||||
@@ -661,7 +681,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
quarantineDict[@"LSQuarantineDataURL"] = [NSURL URLWithString:dataURLString];
|
||||
quarantineDict[@"LSQuarantineOriginURL"] = [NSURL URLWithString:originURLString];
|
||||
quarantineDict[@"LSQuarantineTimestamp"] =
|
||||
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
|
||||
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
|
||||
|
||||
self.quarantineDict = quarantineDict;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -92,9 +90,8 @@
|
||||
}
|
||||
|
||||
- (void)testKext {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:
|
||||
@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc]
|
||||
initWithPath:@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isKext);
|
||||
@@ -106,7 +103,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);
|
||||
@@ -220,8 +217,7 @@
|
||||
}
|
||||
|
||||
- (void)testNonBundle {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
|
||||
@@ -231,10 +227,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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,18 @@
|
||||
/// 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"
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
@@ -88,10 +89,9 @@ typedef enum {
|
||||
ACTION_ERROR = 99,
|
||||
} santa_action_t;
|
||||
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || \
|
||||
x == ACTION_RESPOND_DENY || \
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER || \
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY || \
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER || \
|
||||
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
|
||||
|
||||
// Struct to manage vnode IDs
|
||||
@@ -100,11 +100,11 @@ typedef struct santa_vnode_id_t {
|
||||
uint64_t fileid;
|
||||
|
||||
#ifdef __cplusplus
|
||||
bool operator==(const santa_vnode_id_t& rhs) const {
|
||||
bool operator==(const santa_vnode_id_t &rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
// This _must not_ be used for anything security-sensitive. It exists solely to make
|
||||
// the msleep/wakeup calls easier.
|
||||
// This _must not_ be used for anything security-sensitive. It exists solely
|
||||
// to make the msleep/wakeup calls easier.
|
||||
uint64_t unsafe_simple_id() const {
|
||||
return (((uint64_t)fsid << 32) | fileid);
|
||||
}
|
||||
@@ -118,6 +118,7 @@ typedef struct {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
int pidversion;
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
@@ -127,10 +128,12 @@ typedef struct {
|
||||
// actually happens, so only take MAXPATHLEN and throw away any excess.
|
||||
char pname[MAXPATHLEN];
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to a copy of the message.
|
||||
// For messages that originate from EndpointSecurity, this points to a copy of
|
||||
// the message.
|
||||
void *es_message;
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to an NSArray of the arguments.
|
||||
// For messages that originate from EndpointSecurity, this points to an
|
||||
// NSArray of the arguments.
|
||||
void *args_array;
|
||||
} santa_message_t;
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ typedef enum : NSUInteger {
|
||||
/// @param ... the arguments to format.
|
||||
///
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
__attribute__((format(__NSString__, 3, 4)));
|
||||
__attribute__((format(__NSString__, 3, 4)));
|
||||
|
||||
/// Simple logging macros
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
|
||||
@@ -65,7 +65,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
|
||||
@@ -39,8 +41,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
dispatch_once(&pred, ^{
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
if ([SNTConfigurator configurator].enableDebugLogging) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
@@ -83,11 +84,14 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
break;
|
||||
case LOG_LEVEL_INFO:
|
||||
levelName = "I";
|
||||
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
|
||||
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
173
Source/common/SNTMetricSet.h
Normal file
173
Source/common/SNTMetricSet.h
Normal file
@@ -0,0 +1,173 @@
|
||||
/// 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>
|
||||
|
||||
/**
|
||||
* Provides an abstraction for various metric systems that will be exported to
|
||||
* monitoring systems via the MetricService. This is used to store internal
|
||||
* counters and metrics that can be exported to an external monitoring system.
|
||||
*
|
||||
* `SNTMetricSet` for storing and creating metrics and counters. This is
|
||||
* the externally visible interface
|
||||
* class.
|
||||
*
|
||||
* Metric classes:
|
||||
* * `SNTMetric` to store metric values broken down by "field" dimensions.
|
||||
* * subclasses of `SNTMetric` with suitable setters:
|
||||
* * `SNTMetricCounter`
|
||||
* * `SNTMetricGaugeInt64`
|
||||
* * `SNTMetricGaugeDouble`
|
||||
* * `SNTMetricString`
|
||||
* * `SNTMetricBool`
|
||||
*/
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricType) {
|
||||
SNTMetricTypeUnknown = 0,
|
||||
SNTMetricTypeConstantBool = 1,
|
||||
SNTMetricTypeConstantString = 2,
|
||||
SNTMetricTypeConstantInt64 = 3,
|
||||
SNTMetricTypeConstantDouble = 4,
|
||||
SNTMetricTypeGaugeBool = 5,
|
||||
SNTMetricTypeGaugeString = 6,
|
||||
SNTMetricTypeGaugeInt64 = 7,
|
||||
SNTMetricTypeGaugeDouble = 8,
|
||||
SNTMetricTypeCounter = 9,
|
||||
};
|
||||
|
||||
@interface SNTMetric : NSObject
|
||||
- (NSDictionary *)export;
|
||||
@end
|
||||
|
||||
@interface SNTMetricCounter : SNTMetric
|
||||
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricInt64Gauge : SNTMetric
|
||||
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricDoubleGauge : SNTMetric
|
||||
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricStringGauge : SNTMetric
|
||||
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricBooleanGauge : SNTMetric
|
||||
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
/**
|
||||
* A registry of metrics with associated fields.
|
||||
*/
|
||||
@interface SNTMetricSet : NSObject
|
||||
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username;
|
||||
|
||||
/* Returns a counter with the given name, field names and help
|
||||
* text, registered with the MetricSet.
|
||||
*
|
||||
* @param name The counter name, for example @"/proc/cpu".
|
||||
* @param fieldNames The counter's field names, for example @[@"result"].
|
||||
* @param helpText The counter's help description.
|
||||
* @return A counter with the given specification registered with this root.
|
||||
* The returned counter might have been created earlier with the same
|
||||
* specification.
|
||||
* @throw NSInternalInconsistencyException When trying to register a second
|
||||
* counter with the same name but a different schema as an existing one
|
||||
*/
|
||||
- (SNTMetricCounter *)counterWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text;
|
||||
|
||||
- (void)addRootLabel:(NSString *)label value:(NSString *)value;
|
||||
|
||||
/**
|
||||
* Returns a int64 gauge metric with the given Streamz name and help text,
|
||||
* registered with this MetricSet.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a double gauge metric with the given name and help text,
|
||||
* registered with this root.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a string gauge metric with the given name and help text,
|
||||
* registered with this metric set.
|
||||
*
|
||||
* @param name The metric name, for example @"/santa/mode".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a boolean gauge metric with the given name and help text,
|
||||
* registered with this metric set.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/** Creates a constant metric with a string value and no fields. */
|
||||
- (void)addConstantStringWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(NSString *)value;
|
||||
|
||||
/** Creates a constant metric with an integer value and no fields. */
|
||||
- (void)addConstantIntegerWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(long long)value;
|
||||
|
||||
/** Creates a constant metric with an integer value and no fields. */
|
||||
- (void)addConstantBooleanWithName:(NSString *)name helpText:(NSString *)helpText value:(BOOL)value;
|
||||
|
||||
/** Register a callback to get executed just before each export. */
|
||||
- (void)registerCallback:(void (^)(void))callback;
|
||||
|
||||
/** Export creates an NSDictionary of the state of the metrics */
|
||||
- (NSDictionary *)export;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
599
Source/common/SNTMetricSet.m
Normal file
599
Source/common/SNTMetricSet.m
Normal file
@@ -0,0 +1,599 @@
|
||||
/// 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 "SNTMetricSet.h"
|
||||
|
||||
/**
|
||||
* SNTMetricValue encapsulates the value of a metric along with the creation
|
||||
* and update timestamps. It is thread-safe and has a separate field for each
|
||||
* metric type.
|
||||
*
|
||||
* It is intended to only be used by SNTMetrics;
|
||||
*/
|
||||
@interface SNTMetricValue : NSObject
|
||||
/** Increment the counter by the step value, updating timestamps appropriately. */
|
||||
- (void)addInt64:(long long)step;
|
||||
|
||||
/** Set the Int64 value. */
|
||||
- (void)setInt64:(long long)value;
|
||||
|
||||
/** Set the double value. */
|
||||
- (void)setDouble:(double)value;
|
||||
|
||||
/** Set the string value. */
|
||||
- (void)setString:(NSString *)value;
|
||||
|
||||
/** Set the BOOL string value. */
|
||||
- (void)setBool:(BOOL)value;
|
||||
|
||||
/**
|
||||
* Clears the last update timestamp.
|
||||
*
|
||||
* This makes the metric value always emit the current timestamp as last update timestamp.
|
||||
*/
|
||||
- (void)clearLastUpdateTimestamp;
|
||||
|
||||
/** Getters */
|
||||
- (long long)getInt64Value;
|
||||
- (double)getDoubleValue;
|
||||
- (NSString *)getStringValue;
|
||||
@end
|
||||
|
||||
@implementation SNTMetricValue {
|
||||
/** The int64 value for the SNTMetricValue, if set. */
|
||||
long long _int64Value;
|
||||
|
||||
/** The double value for the SNTMetricValue, if set. */
|
||||
double _doubleValue;
|
||||
|
||||
/** The string value for the SNTMetricValue, if set. */
|
||||
NSString *_stringValue;
|
||||
|
||||
/** The boolean value for the SNTMetricValue, if set. */
|
||||
BOOL _boolValue;
|
||||
|
||||
/** The first time this cell got created in the current process. */
|
||||
NSDate *_creationTime;
|
||||
|
||||
/** The last time that the counter value was changed. */
|
||||
NSDate *_lastUpdate;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_creationTime = [NSDate date];
|
||||
_lastUpdate = _creationTime;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addInt64:(long long)step {
|
||||
@synchronized(self) {
|
||||
_int64Value += step;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInt64:(long long)value {
|
||||
@synchronized(self) {
|
||||
_int64Value = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (long long)getInt64Value {
|
||||
@synchronized(self) {
|
||||
return _int64Value;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDouble:(double)value {
|
||||
@synchronized(self) {
|
||||
_doubleValue = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (double)getDoubleValue {
|
||||
@synchronized(self) {
|
||||
return _doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setString:(NSString *)value {
|
||||
@synchronized(self) {
|
||||
_stringValue = [value copy];
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getStringValue {
|
||||
@synchronized(self) {
|
||||
return [_stringValue copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBool:(BOOL)value {
|
||||
@synchronized(self) {
|
||||
_boolValue = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)getBoolValue {
|
||||
@synchronized(self) {
|
||||
return _boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearLastUpdateTimestamp {
|
||||
@synchronized(self) {
|
||||
_lastUpdate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDate *)getLastUpdatedTimestamp {
|
||||
NSDate *updated = nil;
|
||||
@synchronized(self) {
|
||||
updated = [_lastUpdate copy];
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
- (NSDate *)getCreatedTimestamp {
|
||||
NSDate *created = nil;
|
||||
@synchronized(self) {
|
||||
created = [_creationTime copy];
|
||||
}
|
||||
return created;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetric {
|
||||
@private
|
||||
/** Fully qualified metric name e.g. /ops/security/santa. */
|
||||
NSString *_name;
|
||||
/** A help text for the metric to be exported to be exported. **/
|
||||
NSString *_help;
|
||||
|
||||
/** Sorted list of the fieldNames **/
|
||||
NSArray<NSString *> *_fieldNames;
|
||||
/** Mapping of field values to actual metric values (e.g. metric /proc/cpu_usage @"mode"=@"user"
|
||||
* -> 0.89 */
|
||||
NSMutableDictionary<NSArray<NSString *> *, SNTMetricValue *> *_metricsForFieldValues;
|
||||
/** the type of metric this is e.g. counter, gauge etc. **/
|
||||
SNTMetricType _type;
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)help
|
||||
type:(SNTMetricType)type {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = [name copy];
|
||||
_help = [help copy];
|
||||
_fieldNames = [fieldNames copy];
|
||||
_metricsForFieldValues = [[NSMutableDictionary alloc] init];
|
||||
_type = type;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)name {
|
||||
return _name;
|
||||
}
|
||||
|
||||
- (BOOL)hasSameSchemaAsMetric:(SNTMetric *)other {
|
||||
if (![other isKindOfClass:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
return [_name isEqualToString:other->_name] && [_help isEqualToString:other->_help] &&
|
||||
[_fieldNames isEqualTo:other->_fieldNames] && _type == other->_type;
|
||||
}
|
||||
|
||||
/** Retrieves the SNTMetricValue for a given field value.
|
||||
Creates a new SNTMetricValue if none is present. */
|
||||
- (SNTMetricValue *)metricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
NSParameterAssert(fieldValues.count == _fieldNames.count);
|
||||
SNTMetricValue *metricValue = nil;
|
||||
@synchronized(self) {
|
||||
metricValue = _metricsForFieldValues[fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
// Deep copy to prevent mutations to the keys we store in the dictionary.
|
||||
fieldValues = [fieldValues copy];
|
||||
metricValue = [[SNTMetricValue alloc] init];
|
||||
_metricsForFieldValues[fieldValues] = metricValue;
|
||||
}
|
||||
}
|
||||
|
||||
return metricValue;
|
||||
}
|
||||
|
||||
- (NSDictionary *)encodeMetricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = _metricsForFieldValues[fieldValues];
|
||||
|
||||
NSMutableDictionary *fieldDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
fieldDict[@"created"] = [metricValue getCreatedTimestamp];
|
||||
fieldDict[@"last_updated"] = [metricValue getLastUpdatedTimestamp];
|
||||
fieldDict[@"value"] = [fieldValues componentsJoinedByString:@","];
|
||||
|
||||
switch (_type) {
|
||||
case SNTMetricTypeConstantBool:
|
||||
case SNTMetricTypeGaugeBool:
|
||||
fieldDict[@"data"] = [NSNumber numberWithBool:[metricValue getBoolValue]];
|
||||
break;
|
||||
case SNTMetricTypeConstantInt64:
|
||||
case SNTMetricTypeCounter:
|
||||
case SNTMetricTypeGaugeInt64:
|
||||
fieldDict[@"data"] = [NSNumber numberWithLongLong:[metricValue getInt64Value]];
|
||||
break;
|
||||
case SNTMetricTypeConstantDouble:
|
||||
case SNTMetricTypeGaugeDouble:
|
||||
fieldDict[@"data"] = [NSNumber numberWithDouble:[metricValue getDoubleValue]];
|
||||
break;
|
||||
case SNTMetricTypeConstantString:
|
||||
case SNTMetricTypeGaugeString: fieldDict[@"data"] = [metricValue getStringValue]; break;
|
||||
default: break;
|
||||
}
|
||||
return fieldDict;
|
||||
}
|
||||
|
||||
- (NSDictionary *)export {
|
||||
NSMutableDictionary *metricDict = [NSMutableDictionary dictionaryWithCapacity:_fieldNames.count];
|
||||
metricDict[@"type"] = [NSNumber numberWithInt:(int)_type];
|
||||
metricDict[@"fields"] = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if (_fieldNames.count == 0) {
|
||||
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
|
||||
} else {
|
||||
for (NSString *fieldName in _fieldNames) {
|
||||
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
|
||||
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
|
||||
}
|
||||
|
||||
metricDict[@"fields"][fieldName] = fieldVals;
|
||||
}
|
||||
}
|
||||
return metricDict;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricCounter
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeCounter];
|
||||
}
|
||||
|
||||
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return;
|
||||
}
|
||||
[metricValue addInt64:step];
|
||||
}
|
||||
|
||||
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
[self incrementBy:1 forFieldValues:fieldValues];
|
||||
}
|
||||
|
||||
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getInt64Value];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricInt64Gauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeGaugeInt64];
|
||||
}
|
||||
|
||||
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setInt64:value];
|
||||
}
|
||||
|
||||
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getInt64Value];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricDoubleGauge
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:text
|
||||
type:SNTMetricTypeGaugeDouble];
|
||||
}
|
||||
|
||||
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setDouble:value];
|
||||
}
|
||||
|
||||
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getDoubleValue];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricStringGauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:text
|
||||
type:SNTMetricTypeGaugeString];
|
||||
}
|
||||
|
||||
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setString:value];
|
||||
}
|
||||
|
||||
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [metricValue getStringValue];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricBooleanGauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeGaugeBool];
|
||||
}
|
||||
|
||||
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setBool:value];
|
||||
}
|
||||
|
||||
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [metricValue getBoolValue];
|
||||
}
|
||||
@end
|
||||
|
||||
/**
|
||||
* SNTMetricSet is the top level container for all metrics and metrics value
|
||||
* its is abstracted from specific implementations but is close to Google's
|
||||
* Monarch and Prometheus formats.
|
||||
*/
|
||||
@implementation SNTMetricSet {
|
||||
@private
|
||||
/** Labels that are used to identify the entity to that all metrics apply to. */
|
||||
NSMutableDictionary<NSString *, NSString *> *_rootLabels;
|
||||
/** Registered metrics keyed by name */
|
||||
NSMutableDictionary<NSString *, SNTMetric *> *_metrics;
|
||||
|
||||
/** Callbacks to update metric values before exporting metrics */
|
||||
NSMutableArray<void (^)(void)> *_callbacks;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_rootLabels = [[NSMutableDictionary alloc] init];
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
_callbacks = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_rootLabels = [[NSMutableDictionary alloc] init];
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
_callbacks = [[NSMutableArray alloc] init];
|
||||
|
||||
_rootLabels[@"hostname"] = [hostname copy];
|
||||
_rootLabels[@"username"] = [username copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addRootLabel:(NSString *)label value:(NSString *)value {
|
||||
@synchronized(self) {
|
||||
_rootLabels[label] = value;
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTMetric *)registerMetric:(nonnull SNTMetric *)metric {
|
||||
@synchronized(self) {
|
||||
SNTMetric *oldMetric = _metrics[[metric name]];
|
||||
if ([oldMetric hasSameSchemaAsMetric:metric]) {
|
||||
return oldMetric;
|
||||
}
|
||||
NSAssert(!oldMetric, @"metric registered twice: %@", metric.name);
|
||||
_metrics[metric.name] = metric;
|
||||
}
|
||||
return metric;
|
||||
}
|
||||
|
||||
- (void)registerCallback:(void (^)(void))callback {
|
||||
@synchronized(self) {
|
||||
[_callbacks addObject:callback];
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTMetricCounter *)counterWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricCounter *c = [[SNTMetricCounter alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
[self registerMetric:c];
|
||||
return c;
|
||||
}
|
||||
|
||||
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricInt64Gauge *g = [[SNTMetricInt64Gauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
[self registerMetric:g];
|
||||
return g;
|
||||
}
|
||||
|
||||
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricDoubleGauge *g = [[SNTMetricDoubleGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
[self registerMetric:g];
|
||||
return g;
|
||||
}
|
||||
|
||||
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricStringGauge *s = [[SNTMetricStringGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
[self registerMetric:s];
|
||||
return s;
|
||||
}
|
||||
|
||||
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricBooleanGauge *b = [[SNTMetricBooleanGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
[self registerMetric:b];
|
||||
return b;
|
||||
}
|
||||
|
||||
- (void)addConstantStringWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(NSString *)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantString];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setString:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
- (void)addConstantIntegerWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(long long)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantInt64];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setInt64:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
- (void)addConstantBooleanWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(BOOL)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantBool];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setBool:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
/** Export current state of the SNTMetricSet as an NSDictionary. */
|
||||
- (NSDictionary *)export {
|
||||
NSDictionary *exported = nil;
|
||||
|
||||
// Invoke callbacks to ensure metrics are up to date.
|
||||
for (void (^cb)(void) in _callbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
// Walk root labels
|
||||
NSMutableDictionary *exportDict = [[NSMutableDictionary alloc] init];
|
||||
exportDict[@"root_labels"] = [NSDictionary dictionaryWithDictionary:_rootLabels];
|
||||
exportDict[@"metrics"] = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// sort the metrics so we always get the same output.
|
||||
for (id metricName in _metrics) {
|
||||
SNTMetric *metric = [_metrics objectForKey:metricName];
|
||||
exportDict[@"metrics"][metricName] = [metric export];
|
||||
}
|
||||
|
||||
exported = [NSDictionary dictionaryWithDictionary:exportDict];
|
||||
}
|
||||
return exported;
|
||||
}
|
||||
@end
|
||||
526
Source/common/SNTMetricSetTest.m
Normal file
526
Source/common/SNTMetricSetTest.m
Normal file
@@ -0,0 +1,526 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTMetricSet.h"
|
||||
|
||||
@interface SNTMetricCounterTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricGaugeInt64Test : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricDoubleGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricBooleanGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricStringGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricSetTest : XCTestCase
|
||||
@end
|
||||
|
||||
// Stub out NSDate's date method
|
||||
@implementation NSDate (custom)
|
||||
|
||||
+ (instancetype)date {
|
||||
NSDateFormatter *formatter = NSDateFormatter.new;
|
||||
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
|
||||
return [formatter dateFromString:@"2021-08-05 13:00:10+0000"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricCounterTest
|
||||
- (void)testSimpleCounter {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of exec events broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(c, @"Expected returned SNTMetricCounter to not be nil");
|
||||
[c incrementForFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(1, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremendted by 1");
|
||||
[c incrementBy:3 forFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(4, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremendted by 3");
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of exec events broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(c);
|
||||
[c incrementForFieldValues:@[ @"certificate" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[ @{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([c export], expected);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricBooleanGaugeTest
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
XCTAssertNotNil(b);
|
||||
[b set:true forFieldValues:@[]];
|
||||
XCTAssertTrue([b getBoolValueForFieldValues:@[]]);
|
||||
[b set:false forFieldValues:@[]];
|
||||
XCTAssertFalse([b getBoolValueForFieldValues:@[]]);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
XCTAssertNotNil(b);
|
||||
[b set:true forFieldValues:@[]];
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeBool],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:true]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
NSDictionary *output = [b export];
|
||||
XCTAssertEqualObjects(output, expected);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricGaugeInt64Test
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *g =
|
||||
[metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of rules broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
|
||||
// set from zero
|
||||
[g set:250 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
|
||||
// Increase the gauge
|
||||
[g set:500 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(500, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// Decrease after increase
|
||||
[g set:100 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(100, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// Increase after decrease
|
||||
[g set:750 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(750, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// TODO: export the tree to JSON and confirm the structure is correct.
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *g =
|
||||
[metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of rules broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
|
||||
// set from zero
|
||||
[g set:250 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[ @{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:250]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([g export], expected);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricDoubleGaugeTest
|
||||
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
|
||||
fieldNames:@[ @"mode" ]
|
||||
helpText:@"CPU time consumed by this process."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
|
||||
// set from zero
|
||||
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.45, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
|
||||
// Increase the gauge
|
||||
[g set:(double)0.90 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.90, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
// Decrease after increase
|
||||
[g set:0.71 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.71, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
// Increase after decrease
|
||||
[g set:0.75 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.75, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
|
||||
fieldNames:@[ @"mode" ]
|
||||
helpText:@"CPU time consumed by this process."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
|
||||
// set from zero
|
||||
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
|
||||
[g set:(double)0.90 forFieldValues:@[ @"system" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeDouble],
|
||||
@"fields" : @{
|
||||
@"mode" : @[
|
||||
@{
|
||||
@"value" : @"user",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithDouble:0.45]
|
||||
},
|
||||
@{
|
||||
@"value" : @"system",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithDouble:0.90]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
XCTAssertEqualObjects([g export], expected);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricStringGaugeTest
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
|
||||
fieldNames:@[]
|
||||
helpText:@"String description of the mode."];
|
||||
|
||||
XCTAssertNotNil(s);
|
||||
[s set:@"testValue" forFieldValues:@[]];
|
||||
XCTAssertEqualObjects([s getStringValueForFieldValues:@[]], @"testValue");
|
||||
}
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
|
||||
fieldNames:@[]
|
||||
helpText:@"String description of the mode."];
|
||||
|
||||
XCTAssertNotNil(s);
|
||||
[s set:@"testValue" forFieldValues:@[]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeString],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"testValue"
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([s export], expected);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricSetTest
|
||||
- (void)testRootLabels {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addRootLabel:@"hostname" value:@"localhost"];
|
||||
|
||||
NSDictionary *expected = @{@"root_labels" : @{@"hostname" : @"localhost"}, @"metrics" : @{}};
|
||||
|
||||
NSDictionary *output = [metricSet export];
|
||||
XCTAssertEqualObjects(output, expected);
|
||||
}
|
||||
|
||||
- (void)testDoubleRegisteringIncompatibleMetricsFails {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c = [metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"field" ]
|
||||
helpText:@"lorem ipsum"];
|
||||
|
||||
XCTAssertNotNil(c);
|
||||
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"incompatible" ]
|
||||
helpText:@"A little help text"],
|
||||
@"Should raise error for incompatible field names");
|
||||
|
||||
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"result" ]
|
||||
helpText:@"INCOMPATIBLE"],
|
||||
@"Should raise error for incompatible help text");
|
||||
}
|
||||
|
||||
- (void)testRegisterCallback {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
// Register a callback metric which increments by one before export
|
||||
SNTMetricInt64Gauge *gauge = [metricSet int64GaugeWithName:@"/foo/bar"
|
||||
fieldNames:@[]
|
||||
helpText:@"Number of callbacks done"];
|
||||
__block int count = 0;
|
||||
[metricSet registerCallback:^(void) {
|
||||
count++;
|
||||
[gauge set:count forFieldValues:@[]];
|
||||
}];
|
||||
|
||||
// ensure the callback is called.
|
||||
[metricSet export];
|
||||
|
||||
XCTAssertEqual([gauge getGaugeValueForFieldValues:@[]], 1);
|
||||
}
|
||||
|
||||
- (void)testAddConstantBool {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addConstantBooleanWithName:@"/tautology"
|
||||
helpText:@"The first rule of tautology club is the first rule"
|
||||
value:YES];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/tautology" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:true]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testAddConstantString {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
|
||||
[metricSet addConstantStringWithName:@"/build/label"
|
||||
helpText:@"Build label for the binary"
|
||||
value:@"20210806.0.1"];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/build/label" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"20210806.0.1"
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testAddConstantInt {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addConstantIntegerWithName:@"/deep/thought/answer"
|
||||
helpText:@"Life, the universe, and everything"
|
||||
value:42];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/deep/thought/answer" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithLongLong:42]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
|
||||
username:@"testUser"];
|
||||
|
||||
// Add constants
|
||||
[metricSet addConstantStringWithName:@"/build/label"
|
||||
helpText:@"Software version running."
|
||||
value:@"20210809.0.1"];
|
||||
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
|
||||
helpText:@"Is santad using the endpoint security framework."
|
||||
value:TRUE];
|
||||
[metricSet addConstantIntegerWithName:@"/proc/birth_timestamp"
|
||||
helpText:@"Start time of this LogDumper instance, in microseconds "
|
||||
@"since epoch"
|
||||
value:(long long)(0x12345668910)];
|
||||
// Add Metrics
|
||||
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of events on the host"];
|
||||
|
||||
[c incrementForFieldValues:@[ @"binary" ]];
|
||||
[c incrementBy:2 forFieldValues:@[ @"certificate" ]];
|
||||
|
||||
SNTMetricInt64Gauge *g = [metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Number of rules."];
|
||||
|
||||
[g set:1 forFieldValues:@[ @"binary" ]];
|
||||
[g set:3 forFieldValues:@[ @"certificate" ]];
|
||||
|
||||
// Add Metrics with callback
|
||||
SNTMetricInt64Gauge *virtualMemoryGauge =
|
||||
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
|
||||
fieldNames:@[]
|
||||
helpText:@"The virtual memory size of this process."];
|
||||
|
||||
SNTMetricInt64Gauge *residentMemoryGauge =
|
||||
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
|
||||
fieldNames:@[]
|
||||
helpText:@"The resident set siz of this process."];
|
||||
|
||||
[metricSet registerCallback:^(void) {
|
||||
[virtualMemoryGauge set:987654321 forFieldValues:@[]];
|
||||
[residentMemoryGauge set:123456789 forFieldValues:@[]];
|
||||
}];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"},
|
||||
@"metrics" : @{
|
||||
@"/build/label" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"20210809.0.1"
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/santa/events" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[
|
||||
@{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
@{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:2],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@"/santa/rules" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[
|
||||
@{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
@{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:3],
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
@"/santa/using_endpoint_security_framework" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:YES]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/proc/birth_timestamp" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithLong:1250999830800]
|
||||
} ]
|
||||
},
|
||||
},
|
||||
@"/proc/memory/virtual_size" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:987654321]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/proc/memory/resident_size" : @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:123456789]
|
||||
} ]
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export], expected);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -16,27 +16,37 @@
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
|
||||
#else
|
||||
|
||||
#include <mutex>
|
||||
#include <string.h>
|
||||
|
||||
#define LOGD(format, ...) // NOP
|
||||
#define LOGE(format, ...) // NOP
|
||||
#include <mutex>
|
||||
|
||||
#define LOGD(format, ...) // NOP
|
||||
#define LOGE(format, ...) // NOP
|
||||
|
||||
#define lck_rw_lock_shared(l) pthread_rwlock_rdlock(&l)
|
||||
#define lck_rw_unlock_shared(l) pthread_rwlock_unlock(&l)
|
||||
#define lck_rw_lock_exclusive(l) pthread_rwlock_wrlock(&l)
|
||||
#define lck_rw_unlock_exclusive(l) pthread_rwlock_unlock(&l)
|
||||
|
||||
#define lck_rw_lock_shared_to_exclusive(l) ({ pthread_rwlock_unlock(&l); false; })
|
||||
#define lck_rw_lock_exclusive_to_shared(l) ({ pthread_rwlock_unlock(&l); pthread_rwlock_rdlock(&l); })
|
||||
#define lck_rw_lock_shared_to_exclusive(l) \
|
||||
({ \
|
||||
pthread_rwlock_unlock(&l); \
|
||||
false; \
|
||||
})
|
||||
#define lck_rw_lock_exclusive_to_shared(l) \
|
||||
({ \
|
||||
pthread_rwlock_unlock(&l); \
|
||||
pthread_rwlock_rdlock(&l); \
|
||||
})
|
||||
|
||||
#define lck_mtx_lock(l) l->lock()
|
||||
#define lck_mtx_unlock(l) l->unlock()
|
||||
#endif // KERNEL
|
||||
#endif // KERNEL
|
||||
|
||||
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
root_ = new SantaPrefixNode();
|
||||
@@ -45,7 +55,8 @@ SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
|
||||
#ifdef KERNEL
|
||||
spt_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
spt_lock_grp_ = lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
|
||||
spt_lock_grp_ =
|
||||
lck_grp_alloc_init("santa-prefix-tree-lock", spt_lock_grp_attr_);
|
||||
spt_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
@@ -57,9 +68,9 @@ SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
}
|
||||
|
||||
IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could overwrite whole
|
||||
// branches of another. HasPrefix is still free to read the tree, until AddPrefix needs to
|
||||
// modify it.
|
||||
// Serialize requests to AddPrefix. Otherwise one AddPrefix thread could
|
||||
// overwrite whole branches of another. HasPrefix is still free to read the
|
||||
// tree, until AddPrefix needs to modify it.
|
||||
lck_mtx_lock(spt_add_lock_);
|
||||
|
||||
// Don't allow an empty prefix.
|
||||
@@ -74,13 +85,13 @@ IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
// If there is a node in the path that is considered a prefix, stop adding.
|
||||
// For our purposes we only care about the shortest path that matches.
|
||||
if (node->isPrefix) break;
|
||||
|
||||
// Only process a byte at a time.
|
||||
uint8_t value = prefix[i];
|
||||
uint8_t value = (uint8_t)prefix[i];
|
||||
|
||||
// Create the child if it does not exist.
|
||||
if (!node->children[value]) {
|
||||
@@ -103,7 +114,7 @@ IOReturn SNTPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
|
||||
|
||||
// Create the rest of the prefix.
|
||||
while (i < len) {
|
||||
value = prefix[i++];
|
||||
value = (uint8_t)prefix[i++];
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
node->children[value] = new_node;
|
||||
@@ -159,7 +170,8 @@ bool SNTPrefixTree::HasPrefix(const char *string) {
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
|
||||
// A well formed tree will always break this loop. Even if string doesn't terminate.
|
||||
// A well formed tree will always break this loop. Even if string doesn't
|
||||
// terminate.
|
||||
const char *p = string;
|
||||
while (*p) {
|
||||
// Only process a byte at a time.
|
||||
@@ -193,8 +205,8 @@ void SNTPrefixTree::Reset() {
|
||||
void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
|
||||
if (!target) return;
|
||||
|
||||
// For deep trees, a recursive approach will generate too many stack frames. Make a "stack"
|
||||
// and walk the tree.
|
||||
// For deep trees, a recursive approach will generate too many stack frames.
|
||||
// Make a "stack" and walk the tree.
|
||||
auto stack = new SantaPrefixNode *[node_count_ + 1];
|
||||
if (!stack) {
|
||||
LOGE("Unable to prune tree!");
|
||||
@@ -206,7 +218,8 @@ void SNTPrefixTree::PruneNode(SantaPrefixNode *target) {
|
||||
// Seed the "stack" with a starting node.
|
||||
stack[count++] = target;
|
||||
|
||||
// Start at the target node and walk the tree to find and delete all the sub-nodes.
|
||||
// Start at the target node and walk the tree to find and delete all the
|
||||
// sub-nodes.
|
||||
while (count) {
|
||||
auto node = stack[--count];
|
||||
|
||||
|
||||
@@ -22,10 +22,11 @@
|
||||
#include <libkern/locks.h>
|
||||
#else
|
||||
// Support for unit testing.
|
||||
#include <mutex>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#endif // KERNEL
|
||||
|
||||
#include <mutex>
|
||||
#endif // KERNEL
|
||||
|
||||
///
|
||||
/// SantaPrefixTree is a simple prefix tree implementation.
|
||||
@@ -54,15 +55,17 @@ class SNTPrefixTree {
|
||||
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
|
||||
///
|
||||
/// The path for "/🤘" would look like this:
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4] -> children[0x98]
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4]
|
||||
/// -> children[0x98]
|
||||
///
|
||||
/// The path for "/dev" is:
|
||||
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
|
||||
///
|
||||
/// Lookups of children are O(1).
|
||||
///
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2 byte), would
|
||||
/// drastically decrease the memory footprint but would double required dereferences.
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2
|
||||
/// byte), would drastically decrease the memory footprint but would double
|
||||
/// required dereferences.
|
||||
///
|
||||
/// TODO(bur): Potentially convert this into a full on radix tree.
|
||||
///
|
||||
@@ -74,8 +77,8 @@ class SNTPrefixTree {
|
||||
|
||||
// PruneNode will remove the passed in node from the tree.
|
||||
// The passed in node and all subnodes will be deleted.
|
||||
// It is the caller's responsibility to reset the pointer to this node (held by the parent).
|
||||
// If the tree is in use grab the exclusive lock.
|
||||
// It is the caller's responsibility to reset the pointer to this node (held
|
||||
// by the parent). If the tree is in use grab the exclusive lock.
|
||||
void PruneNode(SantaPrefixNode *);
|
||||
|
||||
SantaPrefixNode *root_;
|
||||
@@ -91,10 +94,10 @@ class SNTPrefixTree {
|
||||
lck_attr_t *spt_lock_attr_;
|
||||
lck_rw_t *spt_lock_;
|
||||
lck_mtx_t *spt_add_lock_;
|
||||
#else // KERNEL
|
||||
#else // KERNEL
|
||||
pthread_rwlock_t spt_lock_;
|
||||
std::mutex *spt_add_lock_;
|
||||
#endif // KERNEL
|
||||
#endif // KERNEL
|
||||
};
|
||||
|
||||
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
///
|
||||
/// Represents a Rule.
|
||||
///
|
||||
@interface SNTRule : NSObject<NSSecureCoding>
|
||||
@interface SNTRule : NSObject <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// The hash of the object this rule is for
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
@interface SNTRule()
|
||||
@interface SNTRule ()
|
||||
@property(readwrite) NSUInteger timestamp;
|
||||
@end
|
||||
|
||||
@@ -40,11 +40,7 @@
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
self = [self initWithShasum:shasum
|
||||
state:state
|
||||
type:type
|
||||
customMsg:customMsg
|
||||
timestamp:0];
|
||||
self = [self initWithShasum:shasum state:state type:type customMsg:customMsg timestamp:0];
|
||||
// Initialize timestamp to current time if rule is transitive.
|
||||
if (self && state == SNTRuleStateAllowTransitive) {
|
||||
[self resetTimestamp];
|
||||
@@ -52,12 +48,12 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define ENCODE(obj, key) \
|
||||
if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
@@ -105,11 +101,12 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
|
||||
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
|
||||
return
|
||||
[NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
|
||||
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
|
||||
}
|
||||
|
||||
# pragma mark Last-access Timestamp
|
||||
#pragma mark Last-access Timestamp
|
||||
|
||||
- (void)resetTimestamp {
|
||||
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
///
|
||||
@interface SNTStoredEvent : NSObject<NSSecureCoding>
|
||||
@interface SNTStoredEvent : NSObject <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// An index for this event, randomly generated during initialization.
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
|
||||
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define ENCODE(obj, key) \
|
||||
if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
@@ -129,7 +130,7 @@
|
||||
|
||||
- (NSString *)description {
|
||||
return
|
||||
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
|
||||
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define WEAKIFY(var) \
|
||||
__weak __typeof(var) Weak_##var = (var);
|
||||
#define WEAKIFY(var) __weak __typeof(var) Weak_##var = (var);
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
@implementation SNTSystemInfo
|
||||
|
||||
+ (NSString *)serialNumber {
|
||||
io_service_t platformExpert = IOServiceGetMatchingService(
|
||||
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
io_service_t platformExpert =
|
||||
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
if (!platformExpert) return nil;
|
||||
|
||||
NSString *serial = CFBridgingRelease(IORegistryEntryCreateCFProperty(
|
||||
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
|
||||
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
|
||||
|
||||
IOObjectRelease(platformExpert);
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
}
|
||||
|
||||
+ (NSString *)hardwareUUID {
|
||||
io_service_t platformExpert = IOServiceGetMatchingService(
|
||||
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
io_service_t platformExpert =
|
||||
IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
if (!platformExpert) return nil;
|
||||
|
||||
NSString *uuid = CFBridgingRelease(IORegistryEntryCreateCFProperty(
|
||||
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
|
||||
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
|
||||
|
||||
IOObjectRelease(platformExpert);
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
#pragma mark - Internal
|
||||
|
||||
+ (NSDictionary *)_systemVersionDictionary {
|
||||
return [NSDictionary
|
||||
dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
|
||||
return
|
||||
[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -42,7 +42,8 @@ typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNu
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
|
||||
|
||||
///
|
||||
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice know you are done with it.
|
||||
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice
|
||||
/// know you are done with it.
|
||||
///
|
||||
- (void)spindown;
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(hashBundleBinariesForEvent:reply:)
|
||||
argumentIndex:1
|
||||
ofReply:YES];
|
||||
forSelector:@selector(hashBundleBinariesForEvent:reply:)
|
||||
argumentIndex:1
|
||||
ofReply:YES];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(databaseEventsPending:)
|
||||
argumentIndex:0
|
||||
ofReply:YES];
|
||||
forSelector:@selector(databaseEventsPending:)
|
||||
argumentIndex:0
|
||||
ofReply:YES];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTRule class], nil]
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
|
||||
50
Source/common/SNTXPCMetricServiceInterface.h
Normal file
50
Source/common/SNTXPCMetricServiceInterface.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/// 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>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
/// Protocol implemented by the metric service and utilized by santad
|
||||
/// exporting metrics to a monitoring system.
|
||||
@protocol SNTMetricServiceXPC
|
||||
|
||||
///
|
||||
/// @param metrics The current metric/counter values serialized to an NSDictionary.
|
||||
///
|
||||
- (void)exportForMonitoring:(NSDictionary *)metrics;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTXPCMetricServiceInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTMetricServiceXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up
|
||||
/// before returning.
|
||||
///
|
||||
+ (NSXPCInterface *)metricServiceInterface;
|
||||
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with santametricservice.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
42
Source/common/SNTXPCMetricServiceInterface.m
Normal file
42
Source/common/SNTXPCMetricServiceInterface.m
Normal file
@@ -0,0 +1,42 @@
|
||||
/// 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/common/SNTXPCMetricServiceInterface.h"
|
||||
|
||||
|
||||
@implementation SNTXPCMetricServiceInterface
|
||||
|
||||
+ (NSXPCInterface *)metricServiceInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTMetricServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSDictionary class], nil]
|
||||
forSelector:@selector(exportForMonitoring:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
return @"com.google.santa.metricservice";
|
||||
}
|
||||
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:NO];
|
||||
c.remoteInterface = [self metricServiceInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -24,7 +24,8 @@
|
||||
/// TODO(bur): Add more details about the sync.
|
||||
typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
|
||||
|
||||
/// Protocol implemented by syncservice and utilized by daemon and ctl for communication with a sync server.
|
||||
/// Protocol implemented by syncservice and utilized by daemon and ctl for communication with a
|
||||
/// sync server.
|
||||
@protocol SNTSyncServiceXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
@@ -53,4 +54,3 @@ typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:fromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
forSelector:@selector(postEventsToSyncServer:fromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,7 @@
|
||||
///
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary,
|
||||
int64_t certificate,
|
||||
int64_t compiler,
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
|
||||
int64_t transitive))reply;
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
|
||||
|
||||
@@ -23,13 +23,14 @@
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(syncBundleEvent:relatedEvents:)
|
||||
argumentIndex:1
|
||||
ofReply:NO];
|
||||
forSelector:@selector(syncBundleEvent:relatedEvents:)
|
||||
argumentIndex:1
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
|
||||
NSXPCInterface *r =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
|
||||
[self initializeControlInterface:r];
|
||||
|
||||
return r;
|
||||
|
||||
@@ -24,12 +24,15 @@
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <IOKit/IOLib.h>
|
||||
#else // KERNEL
|
||||
#else // KERNEL
|
||||
// Support for unit testing.
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#define panic(args...) printf(args); printf("\n"); abort()
|
||||
#define panic(args...) \
|
||||
printf(args); \
|
||||
printf("\n"); \
|
||||
abort()
|
||||
#define IOMallocAligned(sz, alignment) malloc(sz);
|
||||
#define IOFreeAligned(addr, sz) free(addr)
|
||||
#define OSTestAndSet OSAtomicTestAndSet
|
||||
@@ -38,7 +41,7 @@
|
||||
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif // KERNEL
|
||||
#endif // KERNEL
|
||||
|
||||
/**
|
||||
A type to specialize to help SantaCache with its hashing.
|
||||
@@ -46,12 +49,14 @@
|
||||
The default works for numeric types with a multiplicative hash
|
||||
using a prime near to the golden ratio, per Knuth.
|
||||
*/
|
||||
template<typename T> uint64_t SantaCacheHasher(T const& t) {
|
||||
return t * 11400714819323198549UL;
|
||||
template <typename T>
|
||||
uint64_t SantaCacheHasher(T const &t) {
|
||||
return (uint64_t)t * 11400714819323198549UL;
|
||||
};
|
||||
|
||||
/**
|
||||
A somewhat simple, concurrent linked-list hash table intended for use in IOKit kernel extensions.
|
||||
A somewhat simple, concurrent linked-list hash table intended for use in IOKit
|
||||
kernel extensions.
|
||||
|
||||
The type used for keys must overload the == operator and a specialization of
|
||||
SantaCacheHasher must exist for it.
|
||||
@@ -62,25 +67,29 @@ template<typename T> uint64_t SantaCacheHasher(T const& t) {
|
||||
The number of buckets is calculated as `maximum_size` / `per_bucket`
|
||||
rounded up to the next power of 2. Locking is done per-bucket.
|
||||
*/
|
||||
template<typename KeyT, typename ValueT> class SantaCache {
|
||||
template <typename KeyT, typename ValueT>
|
||||
class SantaCache {
|
||||
public:
|
||||
/**
|
||||
Initialize a newly created cache.
|
||||
|
||||
@param maximum_size The maximum number of entries in this cache. Once this
|
||||
number is reached all the entries will be purged.
|
||||
@param per_bucket The target number of entries in each bucket when cache is full.
|
||||
A higher number will result in better performance but higher memory usage.
|
||||
Cannot be higher than 64 to try and ensure buckets don't overflow.
|
||||
@param per_bucket The target number of entries in each bucket when cache is
|
||||
full. A higher number will result in better performance but higher memory
|
||||
usage. Cannot be higher than 64 to try and ensure buckets don't overflow.
|
||||
*/
|
||||
SantaCache(uint64_t maximum_size = 10000, uint8_t per_bucket = 5) {
|
||||
if (unlikely(per_bucket > maximum_size)) per_bucket = maximum_size;
|
||||
if (unlikely(per_bucket > maximum_size)) per_bucket = (uint8_t)maximum_size;
|
||||
if (unlikely(per_bucket < 1)) per_bucket = 1;
|
||||
if (unlikely(per_bucket > 64)) per_bucket = 64;
|
||||
max_size_ = maximum_size;
|
||||
bucket_count_ = (1 << (32 - __builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
|
||||
bucket_count_ =
|
||||
(1 << (32 -
|
||||
__builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
|
||||
if (unlikely(bucket_count_ > UINT32_MAX)) bucket_count_ = UINT32_MAX;
|
||||
buckets_ = (struct bucket *)IOMallocAligned(bucket_count_ * sizeof(struct bucket), 2);
|
||||
buckets_ = (struct bucket *)IOMallocAligned(
|
||||
bucket_count_ * sizeof(struct bucket), 2);
|
||||
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
@@ -122,7 +131,7 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
@return true if the value was set.
|
||||
*/
|
||||
bool set(const KeyT& key, const ValueT& value) {
|
||||
bool set(const KeyT &key, const ValueT &value) {
|
||||
return set(key, value, {}, false);
|
||||
}
|
||||
|
||||
@@ -140,16 +149,14 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
@return true if the value was set
|
||||
*/
|
||||
bool set(const KeyT& key, const ValueT& value, const ValueT& previous_value) {
|
||||
bool set(const KeyT &key, const ValueT &value, const ValueT &previous_value) {
|
||||
return set(key, value, previous_value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
An alias for `set(key, zero_)`
|
||||
*/
|
||||
inline void remove(const KeyT& key) {
|
||||
set(key, zero_);
|
||||
}
|
||||
inline void remove(const KeyT &key) { set(key, zero_); }
|
||||
|
||||
/**
|
||||
Remove all entries and free bucket memory.
|
||||
@@ -183,20 +190,22 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
/**
|
||||
Return number of entries currently in cache.
|
||||
*/
|
||||
inline uint64_t count() const {
|
||||
return count_;
|
||||
}
|
||||
inline uint64_t count() const { return count_; }
|
||||
|
||||
/**
|
||||
Fill in the per_bucket_counts array with the number of entries in each bucket.
|
||||
Fill in the per_bucket_counts array with the number of entries in each
|
||||
bucket.
|
||||
|
||||
The per_buckets_count array will contain the per-bucket counts, up to the number
|
||||
in array_size. The start_bucket parameter will determine which bucket to start off
|
||||
with and upon return will contain either 0 if no buckets are remaining or the next
|
||||
bucket to begin with when called again.
|
||||
The per_buckets_count array will contain the per-bucket counts, up to the
|
||||
number in array_size. The start_bucket parameter will determine which bucket
|
||||
to start off with and upon return will contain either 0 if no buckets are
|
||||
remaining or the next bucket to begin with when called again.
|
||||
*/
|
||||
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
|
||||
if (per_bucket_counts == nullptr || array_size == nullptr || start_bucket == nullptr) return;
|
||||
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size,
|
||||
uint64_t *start_bucket) {
|
||||
if (per_bucket_counts == nullptr || array_size == nullptr ||
|
||||
start_bucket == nullptr)
|
||||
return;
|
||||
|
||||
uint64_t start = *start_bucket;
|
||||
if (start >= bucket_count_) {
|
||||
@@ -205,7 +214,7 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
}
|
||||
|
||||
uint16_t size = *array_size;
|
||||
if (start + size > bucket_count_) size = bucket_count_ - start;
|
||||
if (start + size > bucket_count_) size = (uint16_t)(bucket_count_ - start);
|
||||
|
||||
for (uint16_t i = 0; i < size; ++i) {
|
||||
uint16_t count = 0;
|
||||
@@ -252,8 +261,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
@return true if the entry was set, false if it was not
|
||||
*/
|
||||
bool set(const KeyT& key, const ValueT& value,
|
||||
const ValueT& previous_value, bool has_prev_value) {
|
||||
bool set(const KeyT &key, const ValueT &value, const ValueT &previous_value,
|
||||
bool has_prev_value) {
|
||||
struct bucket *bucket = &buckets_[hash(key)];
|
||||
lock(bucket);
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
@@ -309,7 +318,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
|
||||
// Allocate a new entry, set the key and value, then put this new entry at
|
||||
// the head of this bucket's linked list.
|
||||
struct entry *new_entry = (struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
struct entry *new_entry =
|
||||
(struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
bzero(new_entry, sizeof(struct entry));
|
||||
new_entry->key = key;
|
||||
new_entry->value = value;
|
||||
@@ -325,7 +335,8 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
Lock a bucket. Spins until the lock is acquired.
|
||||
*/
|
||||
inline void lock(struct bucket *bucket) const {
|
||||
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head));
|
||||
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,4 +379,4 @@ template<typename KeyT, typename ValueT> class SantaCache {
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/santa_driver/SantaCache.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
|
||||
@interface SantaCacheTest : XCTestCase
|
||||
@end
|
||||
@@ -102,9 +102,8 @@
|
||||
|
||||
// Calculate stdev
|
||||
double accum = 0.0;
|
||||
std::for_each(per_bucket.begin(), per_bucket.end(), [&](const double d) {
|
||||
accum += (d - mean) * (d - mean);
|
||||
});
|
||||
std::for_each(per_bucket.begin(), per_bucket.end(),
|
||||
[&](const double d) { accum += (d - mean) * (d - mean); });
|
||||
double stddev = sqrt(accum / (per_bucket.size() - 1));
|
||||
double maxStdDev = (double)br / 2;
|
||||
XCTAssertLessThanOrEqual(stddev, maxStdDev,
|
||||
@@ -143,13 +142,15 @@
|
||||
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
for (int i = 0; i < 5000; ++i) sut->set(i, 10000-i);
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
sut->set(i, 10000 - i);
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
for (int i = 5000; i < 10000; ++i) sut->set(i, 10000-i);
|
||||
for (int i = 5000; i < 10000; ++i)
|
||||
sut->set(i, 10000 - i);
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
@@ -157,7 +158,8 @@
|
||||
XCTFail("Timed out while setting values for test");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10000; ++i) XCTAssertEqual(sut->get(i), 10000 - i);
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
XCTAssertEqual(sut->get(i), 10000 - i);
|
||||
}
|
||||
|
||||
delete sut;
|
||||
@@ -193,7 +195,8 @@
|
||||
XCTAssertEqual(sut.get(3.1459124), 0);
|
||||
}
|
||||
|
||||
template<> uint64_t SantaCacheHasher<std::string>(std::string const& s) {
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<std::string>(std::string const &s) {
|
||||
return std::hash<std::string>{}(s);
|
||||
}
|
||||
|
||||
@@ -242,11 +245,12 @@ struct S {
|
||||
uint64_t first_val;
|
||||
uint64_t second_val;
|
||||
|
||||
bool operator==(const S& rhs) {
|
||||
bool operator==(const S &rhs) {
|
||||
return first_val == rhs.first_val && second_val == rhs.second_val;
|
||||
}
|
||||
};
|
||||
template<> uint64_t SantaCacheHasher<S>(S const& s) {
|
||||
template <>
|
||||
uint64_t SantaCacheHasher<S>(S const &s) {
|
||||
return SantaCacheHasher<uint64_t>(s.first_val) ^ (SantaCacheHasher<uint64_t>(s.second_val) << 1);
|
||||
}
|
||||
|
||||
BIN
Source/common/testdata/32bitplist
vendored
Executable file
BIN
Source/common/testdata/32bitplist
vendored
Executable file
Binary file not shown.
@@ -1,11 +1,11 @@
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_application")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
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 = [
|
||||
@@ -35,6 +35,7 @@ objc_library(
|
||||
deps = [
|
||||
"//Source/common:SNTBlockMessage_SantaGUI",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"@MOLCodesignChecker",
|
||||
@@ -54,6 +55,16 @@ macos_application(
|
||||
bundle_name = "Santa",
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.9",
|
||||
entitlements = "Santa.app.entitlements",
|
||||
codesignopts = [
|
||||
"--timestamp",
|
||||
"--force",
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
provisioning_profile = select({
|
||||
"//:ci_build": None,
|
||||
"//conditions:default": "Santa_Dev.provisionprofile",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":SantaGUI_lib"],
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
///
|
||||
/// Initiates and manages the connection to santad
|
||||
///
|
||||
@interface SNTAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@interface SNTAppDelegate : NSObject <NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@@ -43,16 +43,16 @@
|
||||
object:nil
|
||||
queue:[NSOperationQueue currentQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
self.daemonListener.invalidationHandler = nil;
|
||||
[self.daemonListener invalidate];
|
||||
self.daemonListener = nil;
|
||||
}];
|
||||
self.daemonListener.invalidationHandler = nil;
|
||||
[self.daemonListener invalidate];
|
||||
self.daemonListener = nil;
|
||||
}];
|
||||
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
|
||||
object:nil
|
||||
queue:[NSOperationQueue currentQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self attemptDaemonReconnection];
|
||||
}];
|
||||
[self attemptDaemonReconnection];
|
||||
}];
|
||||
|
||||
[self createDaemonConnection];
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
///
|
||||
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.
|
||||
///
|
||||
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate, SNTNotifierXPC>
|
||||
@interface SNTNotificationManager : NSObject <SNTMessageWindowControllerDelegate, SNTNotifierXPC>
|
||||
|
||||
@property NSXPCListenerEndpoint *notificationListener;
|
||||
|
||||
|
||||
@@ -38,14 +38,14 @@
|
||||
|
||||
@implementation SNTNotificationManager
|
||||
|
||||
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_pendingNotifications = [[NSMutableArray alloc] init];
|
||||
_hashBundleBinariesQueue = dispatch_queue_create("com.google.santagui.hashbundlebinaries",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
_hashBundleBinariesQueue =
|
||||
dispatch_queue_create("com.google.santagui.hashbundlebinaries", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -107,8 +107,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
if (!customMsg.length) return;
|
||||
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
}
|
||||
@@ -116,7 +115,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
// See if this binary is already in the list of pending notifications.
|
||||
NSPredicate *predicate =
|
||||
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
|
||||
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
|
||||
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
|
||||
|
||||
// See if this binary is silenced.
|
||||
@@ -145,7 +144,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
// This includes making windows.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SNTMessageWindowController *pendingMsg =
|
||||
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
@@ -179,8 +178,8 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.currentWindowController.foundFileCountLabel.stringValue =
|
||||
[NSString stringWithFormat:@"%llu binaries / %llu %@",
|
||||
binaryCount, hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
|
||||
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
|
||||
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -210,34 +209,39 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[self.currentWindowController.progress becomeCurrentWithPendingUnitCount:100];
|
||||
|
||||
// Start hashing. Progress is reported to the root NSProgress (currentWindowController.progress).
|
||||
[[bc remoteObjectProxy] hashBundleBinariesForEvent:event
|
||||
reply:^(NSString *bh,
|
||||
NSArray<SNTStoredEvent *> *events,
|
||||
NSNumber *ms) {
|
||||
// Revert to displaying the blockable event if we fail to calculate the bundle hash
|
||||
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
|
||||
[[bc remoteObjectProxy]
|
||||
hashBundleBinariesForEvent:event
|
||||
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
|
||||
// Revert to displaying the blockable event if we fail to calculate the
|
||||
// bundle hash
|
||||
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
|
||||
|
||||
event.fileBundleHash = bh;
|
||||
event.fileBundleBinaryCount = @(events.count);
|
||||
event.fileBundleHashMilliseconds = ms;
|
||||
event.fileBundleExecutableRelPath = [events.firstObject fileBundleExecutableRelPath];
|
||||
for (SNTStoredEvent *se in events) {
|
||||
se.fileBundleHash = bh;
|
||||
se.fileBundleBinaryCount = @(events.count);
|
||||
se.fileBundleHashMilliseconds = ms;
|
||||
}
|
||||
event.fileBundleHash = bh;
|
||||
event.fileBundleBinaryCount = @(events.count);
|
||||
event.fileBundleHashMilliseconds = ms;
|
||||
event.fileBundleExecutableRelPath =
|
||||
[events.firstObject fileBundleExecutableRelPath];
|
||||
for (SNTStoredEvent *se in events) {
|
||||
se.fileBundleHash = bh;
|
||||
se.fileBundleBinaryCount = @(events.count);
|
||||
se.fileBundleHashMilliseconds = ms;
|
||||
}
|
||||
|
||||
// Send the results to santad. It will decide if they need to be synced.
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] syncBundleEvent:event relatedEvents:events];
|
||||
[daemonConn invalidate];
|
||||
// Send the results to santad. It will decide if they need to be
|
||||
// synced.
|
||||
MOLXPCConnection *daemonConn =
|
||||
[SNTXPCControlInterface configuredConnection];
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] syncBundleEvent:event
|
||||
relatedEvents:events];
|
||||
[daemonConn invalidate];
|
||||
|
||||
// Update the UI with the bundle hash. Also make the openEventButton available.
|
||||
[self updateBlockNotification:event withBundleHash:bh];
|
||||
// Update the UI with the bundle hash. Also make the openEventButton
|
||||
// available.
|
||||
[self updateBlockNotification:event withBundleHash:bh];
|
||||
|
||||
[bc invalidate];
|
||||
}];
|
||||
[bc invalidate];
|
||||
}];
|
||||
|
||||
[self.currentWindowController.progress resignCurrent];
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santa/SNTAppDelegate.h"
|
||||
|
||||
@interface SNTSystemExtensionDelegate : NSObject<OSSystemExtensionRequestDelegate>
|
||||
@interface SNTSystemExtensionDelegate : NSObject <OSSystemExtensionRequestDelegate>
|
||||
@end
|
||||
|
||||
@implementation SNTSystemExtensionDelegate
|
||||
@@ -28,8 +28,8 @@
|
||||
|
||||
- (OSSystemExtensionReplacementAction)request:(OSSystemExtensionRequest *)request
|
||||
actionForReplacingExtension:(OSSystemExtensionProperties *)old
|
||||
withExtension:(OSSystemExtensionProperties *)new
|
||||
API_AVAILABLE(macos(10.15)) {
|
||||
withExtension:
|
||||
(OSSystemExtensionProperties *)new API_AVAILABLE(macos(10.15)) {
|
||||
NSLog(@"SystemExtension \"%@\" request for replacement", request.identifier);
|
||||
return OSSystemExtensionReplacementActionReplace;
|
||||
}
|
||||
@@ -39,13 +39,13 @@
|
||||
}
|
||||
|
||||
- (void)request:(OSSystemExtensionRequest *)request
|
||||
didFailWithError:(NSError *)error API_AVAILABLE(macos(10.15)) {
|
||||
didFailWithError:(NSError *)error API_AVAILABLE(macos(10.15)) {
|
||||
NSLog(@"SystemExtension \"%@\" request did fail: %@", request.identifier, error);
|
||||
exit((int)error.code);
|
||||
}
|
||||
|
||||
- (void)request:(OSSystemExtensionRequest *)request
|
||||
didFinishWithResult:(OSSystemExtensionRequestResult)result API_AVAILABLE(macos(10.15)) {
|
||||
didFinishWithResult:(OSSystemExtensionRequestResult)result API_AVAILABLE(macos(10.15)) {
|
||||
NSLog(@"SystemExtension \"%@\" request did finish: %ld", request.identifier, (long)result);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -23,6 +22,7 @@ cc_library(
|
||||
copts = [
|
||||
"-mkernel",
|
||||
"-fapple-kext",
|
||||
"-Wno-ossharedptr-misuse",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = [
|
||||
@@ -32,6 +32,7 @@ cc_library(
|
||||
"SANTA_VERSION=" + SANTA_VERSION,
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLoggingKernel",
|
||||
"//Source/common:SNTPrefixTreeKernel",
|
||||
@@ -39,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",
|
||||
|
||||
@@ -616,7 +616,7 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
pid_t pid = proc_pid(proc);
|
||||
pid_t ppid = proc_ppid(proc);
|
||||
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
|
||||
uint64_t val = ((uint64_t)pid << 32) | (ppid & 0xFFFFFFFF);
|
||||
uint64_t val = ((uint64_t)pid << 32) | ((uint64_t)ppid & 0xFFFFFFFF);
|
||||
vnode_pid_map_->set(vnode_id, val);
|
||||
if (returnedAction == ACTION_RESPOND_ALLOW_COMPILER && ppid != 0) {
|
||||
// Do some additional bookkeeping for compilers:
|
||||
@@ -674,8 +674,8 @@ void SantaDecisionManager::FileOpCallback(
|
||||
uint64_t val = vnode_pid_map_->get(vnode_id);
|
||||
if (val) {
|
||||
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
|
||||
message->pid = (val >> 32);
|
||||
message->ppid = (val & ~0xFFFFFFFF00000000);
|
||||
message->pid = (pid_t)(val >> 32);
|
||||
message->ppid = (pid_t)(val & ~0xFFFFFFFF00000000);
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
@@ -816,7 +816,7 @@ extern "C" int vnode_scope_callback(
|
||||
// We only care about regular files.
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
if ((action & KAUTH_VNODE_EXECUTE) && !(action & KAUTH_VNODE_ACCESS)) { // NOLINT
|
||||
if ((action & (int)KAUTH_VNODE_EXECUTE) && !(action & (int)KAUTH_VNODE_ACCESS)) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
@@ -824,9 +824,9 @@ extern "C" int vnode_scope_callback(
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
} else if (action & KAUTH_VNODE_WRITE_DATA || action & KAUTH_VNODE_APPEND_DATA) {
|
||||
} else if (action & (int)KAUTH_VNODE_WRITE_DATA || action & (int)KAUTH_VNODE_APPEND_DATA) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
if (!(action & KAUTH_VNODE_ACCESS)) { // NOLINT
|
||||
if (!(action & (int)KAUTH_VNODE_ACCESS)) {
|
||||
auto vnode_id = sdm->GetVnodeIDForVnode(reinterpret_cast<vfs_context_t>(arg0), vp);
|
||||
sdm->RemoveFromCache(vnode_id);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
#include "Source/santa_driver/SantaCache.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
|
||||
///
|
||||
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
|
||||
@@ -109,8 +109,7 @@ class SantaDecisionManager : public OSObject {
|
||||
uint32_t PidMonitorSleepTimeMilliseconds() const;
|
||||
|
||||
/// Adds a decision to the cache, with a timestamp.
|
||||
void AddToCache(santa_vnode_id_t identifier,
|
||||
const santa_action_t decision,
|
||||
void AddToCache(santa_vnode_id_t identifier, const santa_action_t decision,
|
||||
const uint64_t microsecs = GetCurrentUptime());
|
||||
|
||||
/**
|
||||
@@ -133,17 +132,20 @@ class SantaDecisionManager : public OSObject {
|
||||
void ClearCache(bool non_root_only = false);
|
||||
|
||||
/**
|
||||
Fills out the per_bucket_counts array with the number of items in each bucket in the
|
||||
non-root decision cache.
|
||||
Fills out the per_bucket_counts array with the number of items in each
|
||||
bucket in the non-root decision cache.
|
||||
|
||||
@param per_bucket_counts An array of uint16_t's to fill in with the number of items in each
|
||||
bucket. The size of this array is expected to equal array_size.
|
||||
@param array_size The size of the per_bucket_counts array on input. Upon return this will be
|
||||
updated to the number of slots that were actually used.
|
||||
@param start_bucket If non-zero this is the bucket in the cache to start from. Upon return this
|
||||
will be the next numbered bucket to start from for subsequent requests.
|
||||
@param per_bucket_counts An array of uint16_t's to fill in with the number
|
||||
of items in each bucket. The size of this array is expected to equal
|
||||
array_size.
|
||||
@param array_size The size of the per_bucket_counts array on input. Upon
|
||||
return this will be updated to the number of slots that were actually used.
|
||||
@param start_bucket If non-zero this is the bucket in the cache to start
|
||||
from. Upon return this will be the next numbered bucket to start from for
|
||||
subsequent requests.
|
||||
*/
|
||||
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket);
|
||||
void CacheBucketCount(uint16_t *per_bucket_counts, uint16_t *array_size,
|
||||
uint64_t *start_bucket);
|
||||
|
||||
/// Increments the count of active callbacks pending.
|
||||
void IncrementListenerInvocations();
|
||||
@@ -167,16 +169,15 @@ class SantaDecisionManager : public OSObject {
|
||||
/**
|
||||
Add a file modification prefix filter.
|
||||
*/
|
||||
inline IOReturn FilemodPrefixFilterAdd(const char *prefix, uint64_t *node_count = nullptr) {
|
||||
inline IOReturn FilemodPrefixFilterAdd(const char *prefix,
|
||||
uint64_t *node_count = nullptr) {
|
||||
return filemod_prefix_filter_->AddPrefix(prefix, node_count);
|
||||
}
|
||||
|
||||
/**
|
||||
Reset the file modification prefix filter tree.
|
||||
*/
|
||||
inline void FilemodPrefixFilterReset() {
|
||||
filemod_prefix_filter_->Reset();
|
||||
}
|
||||
inline void FilemodPrefixFilterReset() { filemod_prefix_filter_->Reset(); }
|
||||
|
||||
/**
|
||||
Fetches the vnode_id for a given vnode.
|
||||
@@ -185,16 +186,14 @@ class SantaDecisionManager : public OSObject {
|
||||
@param vp The Vnode to get the ID for
|
||||
@return santa_vnode_id_t The Vnode ID.
|
||||
*/
|
||||
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) {
|
||||
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx,
|
||||
const vnode_t vp) {
|
||||
struct vnode_attr vap;
|
||||
VATTR_INIT(&vap);
|
||||
VATTR_WANTED(&vap, va_fsid);
|
||||
VATTR_WANTED(&vap, va_fileid);
|
||||
vnode_getattr(vp, &vap, ctx);
|
||||
return {
|
||||
.fsid = vap.va_fsid,
|
||||
.fileid = vap.va_fileid
|
||||
};
|
||||
return {.fsid = vap.va_fsid, .fileid = vap.va_fileid};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,8 +215,8 @@ class SantaDecisionManager : public OSObject {
|
||||
@param path The path being operated on.
|
||||
@param new_path The target path for moves and links.
|
||||
*/
|
||||
void FileOpCallback(kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path);
|
||||
void FileOpCallback(kauth_action_t action, const vnode_t vp, const char *path,
|
||||
const char *new_path);
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -227,8 +226,8 @@ class SantaDecisionManager : public OSObject {
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
|
||||
|
||||
/**
|
||||
While waiting for a response from the daemon, this is the maximum number cache checks before
|
||||
re-sending the request.
|
||||
While waiting for a response from the daemon, this is the maximum number
|
||||
cache checks before re-sending the request.
|
||||
*/
|
||||
static const uint32_t kRequestCacheChecks = 5;
|
||||
|
||||
@@ -274,7 +273,8 @@ class SantaDecisionManager : public OSObject {
|
||||
@param identifier The vnode ID string for this request
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t GetFromDaemon(santa_message_t *message, santa_vnode_id_t identifier);
|
||||
santa_action_t GetFromDaemon(santa_message_t *message,
|
||||
santa_vnode_id_t identifier);
|
||||
|
||||
/**
|
||||
Fetches an execution decision for a file, first using the cache and then
|
||||
@@ -287,8 +287,8 @@ class SantaDecisionManager : public OSObject {
|
||||
@param vnode_id The ID for this vnode.
|
||||
@return santa_action_t The response for this request
|
||||
*/
|
||||
santa_action_t FetchDecision(
|
||||
const kauth_cred_t cred, const vnode_t vp, const santa_vnode_id_t vnode_id);
|
||||
santa_action_t FetchDecision(const kauth_cred_t cred, const vnode_t vp,
|
||||
const santa_vnode_id_t vnode_id);
|
||||
|
||||
/**
|
||||
Posts the requested message to the decision data queue.
|
||||
@@ -355,7 +355,8 @@ class SantaDecisionManager : public OSObject {
|
||||
@param identifier The identifier
|
||||
@return SantaCache* The cache to use
|
||||
*/
|
||||
SantaCache<santa_vnode_id_t, uint64_t>* CacheForIdentifier(const santa_vnode_id_t identifier);
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *CacheForIdentifier(
|
||||
const santa_vnode_id_t identifier);
|
||||
|
||||
// This is the file system ID of the root filesystem,
|
||||
// used to determine which cache to use for requests
|
||||
@@ -381,8 +382,9 @@ class SantaDecisionManager : public OSObject {
|
||||
kauth_listener_t vnode_listener_;
|
||||
kauth_listener_t fileop_listener_;
|
||||
|
||||
struct timespec ts_= { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
|
||||
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
|
||||
struct timespec ts_ = {
|
||||
.tv_sec = kRequestLoopSleepMilliseconds / 1000,
|
||||
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -396,14 +398,10 @@ class SantaDecisionManager : public OSObject {
|
||||
@param arg2 Parent Vnode. May be nullptr.
|
||||
@param arg3 Pointer to an errno-style error.
|
||||
*/
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential,
|
||||
void *idata,
|
||||
kauth_action_t action,
|
||||
uintptr_t arg0,
|
||||
uintptr_t arg1,
|
||||
uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
extern "C" int vnode_scope_callback(kauth_cred_t credential, void *idata,
|
||||
kauth_action_t action, uintptr_t arg0,
|
||||
uintptr_t arg1, uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
/**
|
||||
The kauth callback function for the FileOp scope
|
||||
@@ -416,13 +414,9 @@ extern "C" int vnode_scope_callback(
|
||||
@param arg2 depends on action, usually 0.
|
||||
@param arg3 depends on action, usually 0.
|
||||
*/
|
||||
extern "C" int fileop_scope_callback(
|
||||
kauth_cred_t credential,
|
||||
void *idata,
|
||||
kauth_action_t action,
|
||||
uintptr_t arg0,
|
||||
uintptr_t arg1,
|
||||
uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
extern "C" int fileop_scope_callback(kauth_cred_t credential, void *idata,
|
||||
kauth_action_t action, uintptr_t arg0,
|
||||
uintptr_t arg1, uintptr_t arg2,
|
||||
uintptr_t arg3);
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
|
||||
@@ -54,24 +54,23 @@ class com_google_SantaDriverClient : public IOUserClient {
|
||||
IOReturn clientDied() override;
|
||||
|
||||
/// Called during termination
|
||||
bool didTerminate(IOService* provider, IOOptionBits options, bool* defer) override;
|
||||
bool didTerminate(IOService *provider, IOOptionBits options,
|
||||
bool *defer) override;
|
||||
|
||||
/// Called in clients with IOConnectSetNotificationPort
|
||||
IOReturn registerNotificationPort(
|
||||
mach_port_t port, UInt32 type, UInt32 refCon) override;
|
||||
IOReturn registerNotificationPort(mach_port_t port, UInt32 type,
|
||||
UInt32 refCon) override;
|
||||
|
||||
/// Called in clients with IOConnectMapMemory
|
||||
IOReturn clientMemoryForType(
|
||||
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) override;
|
||||
IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options,
|
||||
IOMemoryDescriptor **memory) override;
|
||||
|
||||
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
|
||||
/// to the requested selector using the SantaDriverMethods enum in
|
||||
/// SNTKernelCommon.
|
||||
IOReturn externalMethod(
|
||||
UInt32 selector,
|
||||
IOExternalMethodArguments *arguments,
|
||||
IOExternalMethodDispatch *dispatch,
|
||||
OSObject *target, void *reference) override;
|
||||
IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments,
|
||||
IOExternalMethodDispatch *dispatch, OSObject *target,
|
||||
void *reference) override;
|
||||
|
||||
///
|
||||
/// The userspace callable methods are below. Each method corresponds
|
||||
@@ -79,47 +78,47 @@ class com_google_SantaDriverClient : public IOUserClient {
|
||||
///
|
||||
|
||||
/// Called during client connection.
|
||||
static IOReturn open(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn open(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a binary.
|
||||
static IOReturn allow_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn allow_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a compiler binary.
|
||||
static IOReturn allow_compiler(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn allow_compiler(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to deny a binary.
|
||||
static IOReturn deny_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn deny_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to acknowledge a binary request. This is used for large binaries that
|
||||
/// may take a while to reach a decision.
|
||||
static IOReturn acknowledge_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
/// The daemon calls this to acknowledge a binary request. This is used for
|
||||
/// large binaries that may take a while to reach a decision.
|
||||
static IOReturn acknowledge_binary(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to empty the cache.
|
||||
static IOReturn clear_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn clear_cache(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon call this to remove a single cache entry.
|
||||
static IOReturn remove_cache_entry(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn remove_cache_entry(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in the cache
|
||||
static IOReturn cache_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn cache_count(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out the status of a vnode_id in the cache.
|
||||
/// Output will be a santa_action_t.
|
||||
static IOReturn check_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
static IOReturn check_cache(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in each cache bucket.
|
||||
/// Input and output are both an instance of santa_bucket_count_t.
|
||||
static IOReturn cache_bucket_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
/// The daemon calls this to find out how many items are in each cache
|
||||
/// bucket. Input and output are both an instance of santa_bucket_count_t.
|
||||
static IOReturn cache_bucket_count(OSObject *target, void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to add filemod prefix filters at daemon startup.
|
||||
static IOReturn filemod_prefix_filter_add(
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#include <libkern/OSKextLib.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <libkern/OSKextLib.h>
|
||||
#include <mach/mach.h>
|
||||
#include <numeric>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
@@ -39,24 +39,30 @@
|
||||
/// any existing driver (and daemon) if necessary.
|
||||
///
|
||||
|
||||
#define TSTART(testName) \
|
||||
do { printf(" %-50s ", testName); } while (0)
|
||||
#define TPASS() \
|
||||
do { printf("PASS\n"); } while (0)
|
||||
#define TPASSINFO(fmt, ...) \
|
||||
do { printf("PASS\n " fmt "\n", ##__VA_ARGS__); } while (0)
|
||||
#define TFAIL() \
|
||||
do { \
|
||||
printf("FAIL\n"); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
#define TFAILINFO(fmt, ...) \
|
||||
do { \
|
||||
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
#define TSTART(testName) \
|
||||
do { \
|
||||
printf(" %-50s ", testName); \
|
||||
} while (0)
|
||||
#define TPASS() \
|
||||
do { \
|
||||
printf("PASS\n"); \
|
||||
} while (0)
|
||||
#define TPASSINFO(fmt, ...) \
|
||||
do { \
|
||||
printf("PASS\n " fmt "\n", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#define TFAIL() \
|
||||
do { \
|
||||
printf("FAIL\n"); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
#define TFAILINFO(fmt, ...) \
|
||||
do { \
|
||||
printf("FAIL\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \
|
||||
[self unloadExtension]; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
@interface SantaKernelTests : NSObject
|
||||
@property io_connect_t connection;
|
||||
@@ -88,9 +94,7 @@
|
||||
|
||||
- (NSString *)sha256ForPath:(NSString *)path {
|
||||
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
|
||||
NSData *fData = [NSData dataWithContentsOfFile:path
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:nil];
|
||||
NSData *fData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:nil];
|
||||
CC_SHA256([fData bytes], (unsigned int)[fData length], sha256);
|
||||
char buf[CC_SHA256_DIGEST_LENGTH * 2 + 1];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
|
||||
@@ -105,7 +109,7 @@
|
||||
static NSString *path;
|
||||
if (!path) {
|
||||
NSTask *xcrun = [self taskWithPath:@"/usr/bin/xcrun"];
|
||||
xcrun.arguments = @[@"-f", @"ld"];
|
||||
xcrun.arguments = @[ @"-f", @"ld" ];
|
||||
xcrun.standardOutput = [NSPipe pipe];
|
||||
@try {
|
||||
[xcrun launch];
|
||||
@@ -117,7 +121,7 @@
|
||||
NSData *data = [[xcrun.standardOutput fileHandleForReading] readDataToEndOfFile];
|
||||
if (!data) return nil;
|
||||
path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
|
||||
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
@@ -129,20 +133,20 @@
|
||||
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(santa_vnode_id_t)vnodeid {
|
||||
switch (action) {
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary,
|
||||
&vnodeid, sizeof(vnodeid), 0, 0);
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_DENY:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary,
|
||||
&vnodeid, sizeof(vnodeid), 0, 0);
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ACK:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary,
|
||||
&vnodeid, sizeof(vnodeid), 0, 0);
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAcknowledgeBinary, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler,
|
||||
&vnodeid, sizeof(vnodeid), 0, 0);
|
||||
IOConnectCallStructMethod(self.connection, kSantaUserClientAllowCompiler, &vnodeid,
|
||||
sizeof(vnodeid), 0, 0);
|
||||
break;
|
||||
default:
|
||||
TFAILINFO("postToKernelAction:forVnodeID: received unknown action type: %d", action);
|
||||
@@ -230,8 +234,8 @@
|
||||
TPASS();
|
||||
|
||||
TSTART("Registers the notification port");
|
||||
kern_return_t kr = IOConnectSetNotificationPort(
|
||||
self.connection, QUEUETYPE_DECISION, receivePort, 0);
|
||||
kern_return_t kr =
|
||||
IOConnectSetNotificationPort(self.connection, QUEUETYPE_DECISION, receivePort, 0);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
TFAILINFO("KR: %d", kr);
|
||||
@@ -242,8 +246,8 @@
|
||||
TSTART("Maps shared memory");
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(),
|
||||
&address, &size, kIOMapAnywhere);
|
||||
kr = IOConnectMapMemory(self.connection, QUEUETYPE_DECISION, mach_task_self(), &address, &size,
|
||||
kIOMapAnywhere);
|
||||
if (kr != kIOReturnSuccess) {
|
||||
mach_port_destroy(mach_task_self(), receivePort);
|
||||
TFAILINFO("KR: %d", kr);
|
||||
@@ -292,8 +296,7 @@
|
||||
[ed launch];
|
||||
[ed waitUntilExit];
|
||||
TFAIL();
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
} @catch (NSException *exception) {
|
||||
TPASS();
|
||||
}
|
||||
}
|
||||
@@ -331,7 +334,7 @@
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSString *target =
|
||||
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
|
||||
[[fm currentDirectoryPath] stringByAppendingPathComponent:@"invalidatecachetest"];
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
NSString *edSHA = [self sha256ForPath:@"/bin/ed"];
|
||||
|
||||
@@ -430,10 +433,9 @@
|
||||
// Replace file contents using dd, which doesn't close FDs
|
||||
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
|
||||
NSTask *dd = [self taskWithPath:@"/bin/dd"];
|
||||
dd.arguments = @[ @"if=/bin/ed",
|
||||
@"of=invalidacachetest_tmp",
|
||||
@"bs=1",
|
||||
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
|
||||
dd.arguments = @[
|
||||
@"if=/bin/ed", @"of=invalidacachetest_tmp", @"bs=1",
|
||||
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
|
||||
];
|
||||
[dd launch];
|
||||
[dd waitUntilExit];
|
||||
@@ -494,7 +496,8 @@
|
||||
TFAILINFO("Failed to fork");
|
||||
} else if (pid > 0) {
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0) != pid); // handle EINTR
|
||||
while (waitpid(pid, &status, 0) != pid)
|
||||
; // handle EINTR
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
|
||||
TPASS();
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
@@ -531,17 +534,16 @@
|
||||
|
||||
// Sort and remove first 10 and last 10 entries.
|
||||
std::sort(times.begin(), times.end());
|
||||
times.erase(times.begin(), times.begin()+10);
|
||||
times.erase(times.end()-10, times.end());
|
||||
times.erase(times.begin(), times.begin() + 10);
|
||||
times.erase(times.end() - 10, times.end());
|
||||
|
||||
// Calculate mean
|
||||
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
|
||||
|
||||
// Calculate stdev
|
||||
double accum = 0.0;
|
||||
std::for_each(times.begin(), times.end(), [&](const double d) {
|
||||
accum += (d - mean) * (d - mean);
|
||||
});
|
||||
std::for_each(times.begin(), times.end(),
|
||||
[&](const double d) { accum += (d - mean) * (d - mean); });
|
||||
double stdev = sqrt(accum / (times.size() - 1));
|
||||
|
||||
if (mean > 80 || stdev > 10) {
|
||||
@@ -562,8 +564,8 @@
|
||||
if (calCount++) TFAILINFO("Large binary should not re-request");
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
|
||||
dispatch_get_global_queue(0, 0), ^{
|
||||
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
|
||||
});
|
||||
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:msg.vnode_id];
|
||||
});
|
||||
return ACTION_RESPOND_ACK;
|
||||
}
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
@@ -613,7 +615,7 @@
|
||||
fclose(out);
|
||||
// Then compile it with clang and ld, the latter of which has been marked as a compiler.
|
||||
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
|
||||
clang.arguments = @[@"-o", @"/private/tmp/hello", @"/private/tmp/hello.c"];
|
||||
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
|
||||
[clang launch];
|
||||
[clang waitUntilExit];
|
||||
// Make sure that our version of ld marked as compiler was run. This assumes that
|
||||
@@ -672,7 +674,7 @@
|
||||
fclose(out);
|
||||
// Then compile it with clang and ld, neither of which have been marked as a compiler.
|
||||
NSTask *clang = [self taskWithPath:@"/usr/bin/clang"];
|
||||
clang.arguments = @[@"-o", @"/private/tmp/hello", @"/private/tmp/hello.c"];
|
||||
clang.arguments = @[ @"-o", @"/private/tmp/hello", @"/private/tmp/hello.c" ];
|
||||
@try {
|
||||
[clang launch];
|
||||
[clang waitUntilExit];
|
||||
@@ -709,9 +711,9 @@
|
||||
TSTART("Testing filemod prefix filter");
|
||||
|
||||
NSString *filter =
|
||||
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
|
||||
@"are the same for all non-accelerating observers, and he showed that the speed of light "
|
||||
@"within a vacuum is the same no matter the speed at which an observer travels.";
|
||||
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
|
||||
@"are the same for all non-accelerating observers, and he showed that the speed of light "
|
||||
@"within a vacuum is the same no matter the speed at which an observer travels.";
|
||||
|
||||
// Create a buffer that has 1024 bytes of characters, non-terminating.
|
||||
char buffer[MAXPATHLEN + 1]; // +1 is for the null byte from strlcpy(). It falls off the ledge.
|
||||
@@ -728,8 +730,8 @@
|
||||
// The filter should currently be empty. It is reset when the client disconnects.
|
||||
// Fill up the 1024 node capacity.
|
||||
kern_return_t ret =
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
|
||||
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
|
||||
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
|
||||
|
||||
if (ret != kIOReturnSuccess || n != 1024) {
|
||||
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
|
||||
@@ -794,15 +796,15 @@
|
||||
|
||||
NSString *src = [[fm currentDirectoryPath] stringByAppendingPathComponent:@"santa-driver.kext"];
|
||||
NSString *dest = [NSTemporaryDirectory() stringByAppendingPathComponent:@"santa-driver.kext"];
|
||||
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
|
||||
[fm removeItemAtPath:dest error:NULL]; // ensure dest is free
|
||||
if (![fm copyItemAtPath:src toPath:dest error:&error] || error) {
|
||||
TFAILINFO("Failed to copy kext: %s", error.description.UTF8String);
|
||||
}
|
||||
|
||||
NSDictionary *attrs = @{
|
||||
NSFileOwnerAccountName : @"root",
|
||||
NSFileGroupOwnerAccountName : @"wheel",
|
||||
NSFilePosixPermissions : @0755
|
||||
NSFileOwnerAccountName : @"root",
|
||||
NSFileGroupOwnerAccountName : @"wheel",
|
||||
NSFilePosixPermissions : @0755
|
||||
};
|
||||
|
||||
[fm setAttributes:attrs ofItemAtPath:dest error:NULL];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
licenses(["notice"])
|
||||
|
||||
objc_library(
|
||||
name = "santabs_lib",
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@interface SNTBundleService : NSObject<SNTBundleServiceXPC>
|
||||
@interface SNTBundleService : NSObject <SNTBundleServiceXPC>
|
||||
@end
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
#import "Source/santabundleservice/SNTBundleService.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <pthread/pthread.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
@@ -60,10 +60,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
|
||||
reply:(SNTBundleHashBlock)reply {
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply {
|
||||
NSProgress *progress =
|
||||
[NSProgress currentProgress] ? [NSProgress progressWithTotalUnitCount:100] : nil;
|
||||
[NSProgress currentProgress] ? [NSProgress progressWithTotalUnitCount:100] : nil;
|
||||
|
||||
NSDate *startTime = [NSDate date];
|
||||
|
||||
@@ -92,7 +91,7 @@
|
||||
// For most apps this should be "Contents/MacOS/AppName"
|
||||
if (b.bundle.executablePath.length > b.bundlePath.length) {
|
||||
event.fileBundleExecutableRelPath =
|
||||
[b.bundle.executablePath substringFromIndex:b.bundlePath.length + 1];
|
||||
[b.bundle.executablePath substringFromIndex:b.bundlePath.length + 1];
|
||||
}
|
||||
|
||||
NSDictionary *relatedEvents = [self findRelatedBinaries:event progress:progress];
|
||||
@@ -171,7 +170,7 @@
|
||||
NSString *subpath = subpaths[i];
|
||||
|
||||
NSString *file =
|
||||
[event.fileBundlePath stringByAppendingPathComponent:subpath].stringByStandardizingPath;
|
||||
[event.fileBundlePath stringByAppendingPathComponent:subpath].stringByStandardizingPath;
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithResolvedPath:file error:NULL];
|
||||
if (!fi.isExecutable) return;
|
||||
|
||||
@@ -269,17 +268,15 @@
|
||||
|
||||
NSString *const SHA256FormatString =
|
||||
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
|
||||
NSString *sha256 = [[NSString alloc] initWithFormat:SHA256FormatString,
|
||||
digest[0], digest[1], digest[2], digest[3],
|
||||
digest[4], digest[5], digest[6], digest[7],
|
||||
digest[8], digest[9], digest[10], digest[11],
|
||||
digest[12], digest[13], digest[14], digest[15],
|
||||
digest[16], digest[17], digest[18], digest[19],
|
||||
digest[20], digest[21], digest[22], digest[23],
|
||||
digest[24], digest[25], digest[26], digest[27],
|
||||
digest[28], digest[29], digest[30], digest[31]];
|
||||
NSString *sha256 = [[NSString alloc]
|
||||
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
|
||||
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10], digest[11],
|
||||
digest[12], digest[13], digest[14], digest[15], digest[16], digest[17],
|
||||
digest[18], digest[19], digest[20], digest[21], digest[22], digest[23],
|
||||
digest[24], digest[25], digest[26], digest[27], digest[28], digest[29],
|
||||
digest[30], digest[31]];
|
||||
|
||||
p.completedUnitCount++;
|
||||
[progress resignCurrent];
|
||||
|
||||
@@ -20,15 +20,14 @@
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
#import "Source/santabundleservice/SNTBundleService.h"
|
||||
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
@autoreleasepool {
|
||||
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
|
||||
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);
|
||||
MOLXPCConnection *c =
|
||||
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCBundleServiceInterface serviceID]];
|
||||
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCBundleServiceInterface serviceID]];
|
||||
c.privilegedInterface = c.unprivilegedInterface =
|
||||
[SNTXPCBundleServiceInterface bundleServiceInterface];
|
||||
[SNTXPCBundleServiceInterface bundleServiceInterface];
|
||||
c.exportedObject = [[SNTBundleService alloc] init];
|
||||
[c resume];
|
||||
[[NSRunLoop mainRunLoop] run];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
objc_library(
|
||||
name = "santactl_lib",
|
||||
srcs = [
|
||||
@@ -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,16 +65,30 @@ 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",
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "10.9",
|
||||
codesignopts = [
|
||||
"--timestamp",
|
||||
"--force",
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":santactl_lib"],
|
||||
@@ -130,6 +145,7 @@ santa_unit_test(
|
||||
],
|
||||
sdk_dylibs = ["libz"],
|
||||
deps = [
|
||||
":FCM_lib",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDropRootPrivs",
|
||||
@@ -140,7 +156,6 @@ santa_unit_test(
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCSyncdInterface",
|
||||
"@MOLAuthenticatingURLSession",
|
||||
"@MOLFCMClient",
|
||||
"@MOLXPCConnection",
|
||||
"@OCMock",
|
||||
],
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
@interface SNTCommandBundleInfo : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandBundleInfo : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandBundleInfo
|
||||
@@ -63,21 +63,22 @@ REGISTER_COMMAND_NAME(@"bundleinfo")
|
||||
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
|
||||
[bc resume];
|
||||
|
||||
[[bc remoteObjectProxy] hashBundleBinariesForEvent:se
|
||||
reply:^(NSString *hash,
|
||||
NSArray<SNTStoredEvent *> *events,
|
||||
NSNumber *time) {
|
||||
printf("Hashing time: %llu ms\n", time.unsignedLongLongValue);
|
||||
printf("%lu events found\n", events.count);
|
||||
printf("BundleHash: %s\n", hash.UTF8String);
|
||||
[[bc remoteObjectProxy]
|
||||
hashBundleBinariesForEvent:se
|
||||
reply:^(NSString *hash, NSArray<SNTStoredEvent *> *events,
|
||||
NSNumber *time) {
|
||||
printf("Hashing time: %llu ms\n", time.unsignedLongLongValue);
|
||||
printf("%lu events found\n", events.count);
|
||||
printf("BundleHash: %s\n", hash.UTF8String);
|
||||
|
||||
for (SNTStoredEvent *event in events) {
|
||||
printf("BundleID: %s \n\tSHA-256: %s \n\tPath: %s\n",
|
||||
event.fileBundleID.UTF8String, event.fileSHA256.UTF8String, event.filePath.UTF8String);
|
||||
}
|
||||
[[bc remoteObjectProxy] spindown];
|
||||
exit(0);
|
||||
}];
|
||||
for (SNTStoredEvent *event in events) {
|
||||
printf("BundleID: %s \n\tSHA-256: %s \n\tPath: %s\n",
|
||||
event.fileBundleID.UTF8String, event.fileSHA256.UTF8String,
|
||||
event.filePath.UTF8String);
|
||||
}
|
||||
[[bc remoteObjectProxy] spindown];
|
||||
exit(0);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@interface SNTCommandCacheHistogram : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandCacheHistogram : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandCacheHistogram
|
||||
@@ -50,7 +50,7 @@ REGISTER_COMMAND_NAME(@"cachehistogram")
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
[[self.daemonConn remoteObjectProxy] cacheBucketCount:^(NSArray *counts) {
|
||||
NSMutableDictionary<NSNumber *, NSNumber *> *d = [NSMutableDictionary dictionary];
|
||||
[counts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[counts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
|
||||
d[obj] = @([d[obj] intValue] + 1);
|
||||
}];
|
||||
printf("There are %llu empty buckets\n", [d[@0] unsignedLongLongValue]);
|
||||
|
||||
@@ -22,10 +22,9 @@
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
@interface SNTCommandCheckCache : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandCheckCache : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandCheckCache
|
||||
@@ -51,25 +50,26 @@ REGISTER_COMMAND_NAME(@"checkcache")
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
santa_vnode_id_t vnodeID = [self vnodeIDForFile:arguments.firstObject];
|
||||
[[self.daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
LOGI(@"File exists in [allowlist] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
LOGI(@"File exists in [blocklist] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_ALLOW_COMPILER) {
|
||||
LOGI(@"File exists in [allowlist compiler] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE) {
|
||||
LOGI(@"File exists in [allowlist pending_transitive] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_UNSET) {
|
||||
LOGE(@"File does not exist in cache");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
[[self.daemonConn remoteObjectProxy]
|
||||
checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
LOGI(@"File exists in [allowlist] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
LOGI(@"File exists in [blocklist] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_ALLOW_COMPILER) {
|
||||
LOGI(@"File exists in [allowlist compiler] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE) {
|
||||
LOGI(@"File exists in [allowlist pending_transitive] kernel cache");
|
||||
exit(0);
|
||||
} else if (action == ACTION_UNSET) {
|
||||
LOGE(@"File does not exist in cache");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (santa_vnode_id_t)vnodeIDForFile:(NSString *)path {
|
||||
|
||||
@@ -65,7 +65,7 @@ NSString *formattedStringForKeyArray(NSArray<NSString *> *array) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@interface SNTCommandFileInfo : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandFileInfo : SNTCommand <SNTCommandProtocol>
|
||||
|
||||
// Properties set from commandline flags
|
||||
@property(nonatomic) BOOL recursive;
|
||||
@@ -144,50 +144,53 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return [NSString stringWithFormat:
|
||||
@"The details provided will be the same ones Santa uses to make a decision\n"
|
||||
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
|
||||
@"the type of file."
|
||||
@"\n"
|
||||
@"Usage: santactl fileinfo [options] [file-paths]\n"
|
||||
@" --recursive (-r): Search directories recursively.\n"
|
||||
@" --json: Output in JSON format.\n"
|
||||
@" --key: Search and return this one piece of information.\n"
|
||||
@" You may specify multiple keys by repeating this flag.\n"
|
||||
@" Valid Keys:\n"
|
||||
@"%@\n"
|
||||
@" Valid keys when using --cert-index:\n"
|
||||
@"%@\n"
|
||||
@" --cert-index: Supply an integer corresponding to a certificate of the\n"
|
||||
@" signing chain to show info only for that certificate.\n"
|
||||
@" 1 for the leaf certificate\n"
|
||||
@" -1 for the root certificate\n"
|
||||
@" 2 and up for the intermediates / root\n"
|
||||
@"\n"
|
||||
@" --filter: Use predicates of the form 'key=regex' to filter out which files\n"
|
||||
@" are displayed. Valid keys are the same as for --key. Value is a\n"
|
||||
@" case-insensitive regular expression which must match anywhere in\n"
|
||||
@" the keyed property value for the file's info to be displayed.\n"
|
||||
@" You may specify multiple filters by repeating this flag.\n"
|
||||
@"\n"
|
||||
@"Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo /usr/bin/yes /bin/*\n"
|
||||
@" santactl fileinfo /usr/bin -r --key Path --key SHA-256 --key Rule\n"
|
||||
@" santactl fileinfo /usr/bin/* --filter Type=Script --filter Path=zip",
|
||||
formattedStringForKeyArray(self.fileInfoKeys),
|
||||
formattedStringForKeyArray(self.signingChainKeys)];
|
||||
return
|
||||
[NSString stringWithFormat:
|
||||
@"The details provided will be the same ones Santa uses to make a decision\n"
|
||||
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
|
||||
@"the type of file."
|
||||
@"\n"
|
||||
@"Usage: santactl fileinfo [options] [file-paths]\n"
|
||||
@" --recursive (-r): Search directories recursively.\n"
|
||||
@" --json: Output in JSON format.\n"
|
||||
@" --key: Search and return this one piece of information.\n"
|
||||
@" You may specify multiple keys by repeating this flag.\n"
|
||||
@" Valid Keys:\n"
|
||||
@"%@\n"
|
||||
@" Valid keys when using --cert-index:\n"
|
||||
@"%@\n"
|
||||
@" --cert-index: Supply an integer corresponding to a certificate of the\n"
|
||||
@" signing chain to show info only for that certificate.\n"
|
||||
@" 1 for the leaf certificate\n"
|
||||
@" -1 for the root certificate\n"
|
||||
@" 2 and up for the intermediates / root\n"
|
||||
@"\n"
|
||||
@" --filter: Use predicates of the form 'key=regex' to filter out which files\n"
|
||||
@" are displayed. Valid keys are the same as for --key. Value is a\n"
|
||||
@" case-insensitive regular expression which must match anywhere in\n"
|
||||
@" the keyed property value for the file's info to be displayed.\n"
|
||||
@" You may specify multiple filters by repeating this flag.\n"
|
||||
@"\n"
|
||||
@"Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo --key SHA-256 --json /usr/bin/yes\n"
|
||||
@" santactl fileinfo /usr/bin/yes /bin/*\n"
|
||||
@" santactl fileinfo /usr/bin -r --key Path --key SHA-256 --key Rule\n"
|
||||
@" santactl fileinfo /usr/bin/* --filter Type=Script --filter Path=zip",
|
||||
formattedStringForKeyArray(self.fileInfoKeys),
|
||||
formattedStringForKeyArray(self.signingChainKeys)];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)fileInfoKeys {
|
||||
return @[ kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr,
|
||||
kDownloadReferrerURL, kDownloadURL, kDownloadTimestamp, kDownloadAgent,
|
||||
kType, kPageZero, kCodeSigned, kRule, kSigningChain, kUniversalSigningChain ];
|
||||
return @[
|
||||
kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr, kDownloadReferrerURL,
|
||||
kDownloadURL, kDownloadTimestamp, kDownloadAgent, kType, kPageZero, kCodeSigned, kRule,
|
||||
kSigningChain, kUniversalSigningChain
|
||||
];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)signingChainKeys {
|
||||
return @[ kSHA256, kSHA1, kCommonName, kOrganization, kOrganizationalUnit, kValidFrom,
|
||||
kValidUntil ];
|
||||
return
|
||||
@[ kSHA256, kSHA1, kCommonName, kOrganization, kOrganizationalUnit, kValidFrom, kValidUntil ];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn {
|
||||
@@ -196,22 +199,24 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
_dateFormatter = [[NSDateFormatter alloc] init];
|
||||
_dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
|
||||
_propertyMap = @{ kPath : self.path,
|
||||
kSHA256 : self.sha256,
|
||||
kSHA1 : self.sha1,
|
||||
kBundleName : self.bundleName,
|
||||
kBundleVersion : self.bundleVersion,
|
||||
kBundleVersionStr : self.bundleVersionStr,
|
||||
kDownloadReferrerURL : self.downloadReferrerURL,
|
||||
kDownloadURL : self.downloadURL,
|
||||
kDownloadTimestamp : self.downloadTimestamp,
|
||||
kDownloadAgent : self.downloadAgent,
|
||||
kType : self.type,
|
||||
kPageZero : self.pageZero,
|
||||
kCodeSigned : self.codeSigned,
|
||||
kRule : self.rule,
|
||||
kSigningChain : self.signingChain,
|
||||
kUniversalSigningChain : self.universalSigningChain };
|
||||
_propertyMap = @{
|
||||
kPath : self.path,
|
||||
kSHA256 : self.sha256,
|
||||
kSHA1 : self.sha1,
|
||||
kBundleName : self.bundleName,
|
||||
kBundleVersion : self.bundleVersion,
|
||||
kBundleVersionStr : self.bundleVersionStr,
|
||||
kDownloadReferrerURL : self.downloadReferrerURL,
|
||||
kDownloadURL : self.downloadURL,
|
||||
kDownloadTimestamp : self.downloadTimestamp,
|
||||
kDownloadAgent : self.downloadAgent,
|
||||
kType : self.type,
|
||||
kPageZero : self.pageZero,
|
||||
kCodeSigned : self.codeSigned,
|
||||
kRule : self.rule,
|
||||
kSigningChain : self.signingChain,
|
||||
kUniversalSigningChain : self.universalSigningChain
|
||||
};
|
||||
|
||||
_printQueue = dispatch_queue_create("com.google.santactl.print_queue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
@@ -221,78 +226,78 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
#pragma mark property getters
|
||||
|
||||
- (SNTAttributeBlock)path {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.path;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)sha256 {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.SHA256;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)sha1 {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.SHA1;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleName {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.bundleName;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleVersion {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.bundleVersion;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)bundleVersionStr {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.bundleShortVersionString;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadReferrerURL {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.quarantineRefererURL;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadURL {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.quarantineDataURL;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadTimestamp {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return [cmd.dateFormatter stringFromDate:fileInfo.quarantineTimestamp];
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)downloadAgent {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return fileInfo.quarantineAgentBundleID;
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)type {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
NSArray *archs = [fileInfo architectures];
|
||||
if (archs.count == 0) {
|
||||
return [fileInfo humanReadableFileType];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%@ (%@)",
|
||||
[fileInfo humanReadableFileType], [archs componentsJoinedByString:@", "]];
|
||||
return [NSString stringWithFormat:@"%@ (%@)", [fileInfo humanReadableFileType],
|
||||
[archs componentsJoinedByString:@", "]];
|
||||
};
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)pageZero {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
if ([fileInfo isMissingPageZero]) {
|
||||
return @"__PAGEZERO segment missing/bad!";
|
||||
}
|
||||
@@ -301,37 +306,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)codeSigned {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
NSError *error;
|
||||
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&error];
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
return @"No";
|
||||
case errSecCSUnsigned: return @"No";
|
||||
case errSecCSSignatureFailed:
|
||||
case errSecCSStaticCodeChanged:
|
||||
case errSecCSSignatureNotVerifiable:
|
||||
case errSecCSSignatureUnsupported:
|
||||
return @"Yes, but code/signature changed/unverifiable";
|
||||
case errSecCSSignatureUnsupported: return @"Yes, but code/signature changed/unverifiable";
|
||||
case errSecCSResourceDirectoryFailed:
|
||||
case errSecCSResourceNotSupported:
|
||||
case errSecCSResourceRulesInvalid:
|
||||
case errSecCSResourcesInvalid:
|
||||
case errSecCSResourcesNotFound:
|
||||
case errSecCSResourcesNotSealed:
|
||||
return @"Yes, but resources invalid";
|
||||
case errSecCSResourcesNotSealed: return @"Yes, but resources invalid";
|
||||
case errSecCSReqFailed:
|
||||
case errSecCSReqInvalid:
|
||||
case errSecCSReqUnsupported:
|
||||
return @"Yes, but failed requirement validation";
|
||||
case errSecCSInfoPlistFailed:
|
||||
return @"Yes, but can't validate as Info.plist is missing";
|
||||
case errSecCSReqUnsupported: return @"Yes, but failed requirement validation";
|
||||
case errSecCSInfoPlistFailed: return @"Yes, but can't validate as Info.plist is missing";
|
||||
case errSecCSSignatureInvalid:
|
||||
if ([error.domain isEqualToString:@"com.google.molcodesignchecker"]) {
|
||||
return @"Yes, but signing is not consistent for all architectures";
|
||||
}
|
||||
case CSSMERR_TP_CERT_REVOKED:
|
||||
return @"Yes, but the signing certificate was revoked";
|
||||
case CSSMERR_TP_CERT_REVOKED: return @"Yes, but the signing certificate was revoked";
|
||||
default: {
|
||||
return [NSString stringWithFormat:@"Yes, but failed to validate (%ld)", error.code];
|
||||
}
|
||||
@@ -345,11 +344,13 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)rule {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
// If we previously were unable to connect, don't try again.
|
||||
if (cmd.daemonUnavailable) return kCommunicationErrorMsg;
|
||||
static dispatch_once_t token;
|
||||
dispatch_once(&token, ^{ [cmd.daemonConn resume]; });
|
||||
dispatch_once(&token, ^{
|
||||
[cmd.daemonConn resume];
|
||||
});
|
||||
__block SNTEventState state;
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
NSError *err;
|
||||
@@ -358,41 +359,27 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
fileSHA256:fileInfo.SHA256
|
||||
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
|
||||
reply:^(SNTEventState s) {
|
||||
state = s;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
state = s;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
cmd.daemonUnavailable = YES;
|
||||
return kCommunicationErrorMsg;
|
||||
} else {
|
||||
NSMutableString *output =
|
||||
(SNTEventStateAllow & state) ? @"Allowed".mutableCopy : @"Blocked".mutableCopy;
|
||||
(SNTEventStateAllow & state) ? @"Allowed".mutableCopy : @"Blocked".mutableCopy;
|
||||
switch (state) {
|
||||
case SNTEventStateAllowUnknown:
|
||||
case SNTEventStateBlockUnknown:
|
||||
[output appendString:@" (Unknown)"];
|
||||
break;
|
||||
case SNTEventStateBlockUnknown: [output appendString:@" (Unknown)"]; break;
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateBlockBinary:
|
||||
[output appendString:@" (Binary)"];
|
||||
break;
|
||||
case SNTEventStateBlockBinary: [output appendString:@" (Binary)"]; break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateBlockCertificate:
|
||||
[output appendString:@" (Certificate)"];
|
||||
break;
|
||||
case SNTEventStateBlockCertificate: [output appendString:@" (Certificate)"]; break;
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateBlockScope:
|
||||
[output appendString:@" (Scope)"];
|
||||
break;
|
||||
case SNTEventStateAllowCompiler:
|
||||
[output appendString:@" (Compiler)"];
|
||||
break;
|
||||
case SNTEventStateAllowTransitive:
|
||||
[output appendString:@" (Transitive)"];
|
||||
break;
|
||||
default:
|
||||
output = @"None".mutableCopy;
|
||||
break;
|
||||
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
|
||||
case SNTEventStateAllowCompiler: [output appendString:@" (Compiler)"]; break;
|
||||
case SNTEventStateAllowTransitive: [output appendString:@" (Transitive)"]; break;
|
||||
default: output = @"None".mutableCopy; break;
|
||||
}
|
||||
if (cmd.prettyOutput) {
|
||||
if ((SNTEventStateAllow & state)) {
|
||||
@@ -412,7 +399,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)signingChain {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
|
||||
if (!csc.certificates.count) return nil;
|
||||
NSMutableArray *certs = [[NSMutableArray alloc] initWithCapacity:csc.certificates.count];
|
||||
@@ -432,22 +419,22 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
- (SNTAttributeBlock)universalSigningChain {
|
||||
return ^id (SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
|
||||
if (csc.certificates.count) return nil;
|
||||
if (!csc.universalSigningInformation) return nil;
|
||||
NSMutableArray *universal = [NSMutableArray array];
|
||||
for (NSDictionary *arch in csc.universalSigningInformation) {
|
||||
[universal addObject:@{ @"arch" : arch.allKeys.firstObject }];
|
||||
[universal addObject:@{@"arch" : arch.allKeys.firstObject}];
|
||||
int flags = [arch.allValues.firstObject[(__bridge id)kSecCodeInfoFlags] intValue];
|
||||
if (flags & kSecCodeSignatureAdhoc) {
|
||||
[universal addObject:@{ @"ad-hoc" : @YES }];
|
||||
[universal addObject:@{@"ad-hoc" : @YES}];
|
||||
continue;
|
||||
}
|
||||
NSArray *certs = arch.allValues.firstObject[(__bridge id)kSecCodeInfoCertificates];
|
||||
NSArray *chain = [MOLCertificate certificatesFromArray:certs];
|
||||
if (!chain.count) {
|
||||
[universal addObject:@{ @"unsigned" : @YES }];
|
||||
[universal addObject:@{@"unsigned" : @YES}];
|
||||
continue;
|
||||
}
|
||||
for (MOLCertificate *c in chain) {
|
||||
@@ -471,7 +458,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
};
|
||||
}
|
||||
|
||||
# pragma mark -
|
||||
#pragma mark -
|
||||
|
||||
// Entry point for the command.
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
@@ -505,12 +492,12 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
[filePaths enumerateObjectsWithOptions:NSEnumerationConcurrent
|
||||
usingBlock:^(NSString *path, NSUInteger idx, BOOL *stop) {
|
||||
NSString *fullPath = [path stringByStandardizingPath];
|
||||
if (path.length && [path characterAtIndex:0] != '/') {
|
||||
fullPath = [cwd stringByAppendingPathComponent:fullPath];
|
||||
}
|
||||
[self recurseAtPath:fullPath];
|
||||
}];
|
||||
NSString *fullPath = [path stringByStandardizingPath];
|
||||
if (path.length && [path characterAtIndex:0] != '/') {
|
||||
fullPath = [cwd stringByAppendingPathComponent:fullPath];
|
||||
}
|
||||
[self recurseAtPath:fullPath];
|
||||
}];
|
||||
|
||||
// Wait for all tasks in print queue to complete.
|
||||
dispatch_group_wait(self.printGroup, DISPATCH_TIME_FOREVER);
|
||||
@@ -563,7 +550,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
} else if (isDir && !isBundle) {
|
||||
dispatch_group_async(self.printGroup, self.printQueue, ^{
|
||||
fprintf(stderr, "%s is a directory. Use the -r flag to search recursively.\n",
|
||||
[path UTF8String]);
|
||||
[path UTF8String]);
|
||||
});
|
||||
} else {
|
||||
[operationQueue addOperationWithBlock:^{
|
||||
@@ -646,8 +633,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
// a) do we want JSON output?
|
||||
// b) is there only one key?
|
||||
// c) are we displaying a cert?
|
||||
BOOL singleKey = (self.outputKeyList.count == 1 &&
|
||||
![self.outputKeyList.firstObject isEqual:kSigningChain]);
|
||||
BOOL singleKey =
|
||||
(self.outputKeyList.count == 1 && ![self.outputKeyList.firstObject isEqual:kSigningChain]);
|
||||
NSMutableString *output = [NSMutableString string];
|
||||
if (self.jsonOutput) {
|
||||
[output appendString:[self jsonStringForDictionary:outputDict]];
|
||||
@@ -660,8 +647,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
if (singleKey) {
|
||||
[output appendFormat:@"%@\n", outputDict[key]];
|
||||
} else {
|
||||
[output appendFormat:@"%-*s: %@\n",
|
||||
(int)self.maxKeyWidth, key.UTF8String, outputDict[key]];
|
||||
[output
|
||||
appendFormat:@"%-*s: %@\n", (int)self.maxKeyWidth, key.UTF8String, outputDict[key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -701,9 +688,11 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
int index = 0;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:arguments[i]];
|
||||
if (![scanner scanInt:&index] || !scanner.atEnd || index == 0 || index < -1) {
|
||||
[self printErrorUsageAndExit:[NSString stringWithFormat:
|
||||
@"\n\"%@\" is an invalid argument for --cert-index\n"
|
||||
@" --cert-index argument must be one of -1, 1, 2, 3, ...", arguments[i]]];
|
||||
[self
|
||||
printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid argument for --cert-index\n"
|
||||
@" --cert-index argument must be one of -1, 1, 2, 3, ...",
|
||||
arguments[i]]];
|
||||
}
|
||||
self.certIndex = index;
|
||||
} else if ([arg caseInsensitiveCompare:@"--key"] == NSOrderedSame) {
|
||||
@@ -721,22 +710,24 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
NSRange range = [arguments[i] rangeOfString:@"="];
|
||||
if (range.location == NSNotFound || range.location == 0 ||
|
||||
range.location == arguments[i].length - 1) {
|
||||
[self printErrorUsageAndExit:[NSString stringWithFormat:
|
||||
@"\n\"%@\" is an invalid filter predicate.\n"
|
||||
@"Filter predicates must be of the form key=regex"
|
||||
@" (with no spaces around \"=\")", arguments[i]]];
|
||||
[self printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid filter predicate.\n"
|
||||
@"Filter predicates must be of the form key=regex"
|
||||
@" (with no spaces around \"=\")",
|
||||
arguments[i]]];
|
||||
}
|
||||
NSString *key = [arguments[i] substringToIndex:range.location];
|
||||
NSString *rhs = [arguments[i] substringFromIndex:range.location+1];
|
||||
NSString *rhs = [arguments[i] substringFromIndex:range.location + 1];
|
||||
// Convert right-hand side of '=' into a regular expression object.
|
||||
NSError *error;
|
||||
NSRegularExpression *regex =
|
||||
[NSRegularExpression regularExpressionWithPattern:rhs
|
||||
options:NSRegularExpressionCaseInsensitive
|
||||
error:&error];
|
||||
[NSRegularExpression regularExpressionWithPattern:rhs
|
||||
options:NSRegularExpressionCaseInsensitive
|
||||
error:&error];
|
||||
if (error) {
|
||||
[self printErrorUsageAndExit:[NSString stringWithFormat:
|
||||
@"\n\"%@\" is an invalid regular expression in filter argument.\n", rhs]];
|
||||
[self printErrorUsageAndExit:[NSString stringWithFormat:@"\n\"%@\" is an invalid regular "
|
||||
@"expression in filter argument.\n",
|
||||
rhs]];
|
||||
}
|
||||
filters[key] = regex;
|
||||
} else if ([arg caseInsensitiveCompare:@"--recursive"] == NSOrderedSame ||
|
||||
@@ -752,28 +743,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
NSArray *validKeys = [[self class] signingChainKeys];
|
||||
for (NSString *key in keys) {
|
||||
if (![validKeys containsObject:key]) {
|
||||
[self printErrorUsageAndExit:
|
||||
[self
|
||||
printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid key when using --cert-index", key]];
|
||||
}
|
||||
}
|
||||
for (NSString *key in filters) {
|
||||
if (![validKeys containsObject:key]) {
|
||||
[self printErrorUsageAndExit:[NSString stringWithFormat:
|
||||
@"\n\"%@\" is an invalid filter key when using --cert-index", key]];
|
||||
[self
|
||||
printErrorUsageAndExit:
|
||||
[NSString
|
||||
stringWithFormat:@"\n\"%@\" is an invalid filter key when using --cert-index", key]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSArray *validKeys = [[self class] fileInfoKeys];
|
||||
for (NSString *key in keys) {
|
||||
if (![validKeys containsObject:key]) {
|
||||
[self printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid key", key]];
|
||||
[self
|
||||
printErrorUsageAndExit:[NSString stringWithFormat:@"\n\"%@\" is an invalid key", key]];
|
||||
}
|
||||
}
|
||||
for (NSString *key in filters) {
|
||||
if (![validKeys containsObject:key] || [key isEqualToString:kSigningChain]) {
|
||||
[self printErrorUsageAndExit:
|
||||
[NSString stringWithFormat:@"\n\"%@\" is an invalid filter key", key]];
|
||||
[self printErrorUsageAndExit:[NSString
|
||||
stringWithFormat:@"\n\"%@\" is an invalid filter key", key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,10 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsFilePaths {
|
||||
NSArray *args = @[ @"/usr/bin/yes", @"/bin/mv", @"--key", @"SHA-256", @"/bin/ls", @"--json",
|
||||
@"/bin/rm", @"--cert-index", @"1", @"/bin/cp" ];
|
||||
NSArray *args = @[
|
||||
@"/usr/bin/yes", @"/bin/mv", @"--key", @"SHA-256", @"/bin/ls", @"--json", @"/bin/rm",
|
||||
@"--cert-index", @"1", @"/bin/cp"
|
||||
];
|
||||
NSArray *filePaths = [self.cfi parseArguments:args];
|
||||
XCTAssertEqual(filePaths.count, 5);
|
||||
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
|
||||
@@ -106,7 +108,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsFilePathSameAsKey {
|
||||
NSArray *filePaths = [self.cfi parseArguments:@[ @"--key", @"Rule", @"Rule"]];
|
||||
NSArray *filePaths = [self.cfi parseArguments:@[ @"--key", @"Rule", @"Rule" ]];
|
||||
XCTAssertTrue([self.cfi.outputKeyList containsObject:@"Rule"]);
|
||||
XCTAssertEqual(filePaths.count, 1);
|
||||
XCTAssertTrue([filePaths containsObject:@"Rule"]);
|
||||
@@ -115,134 +117,136 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
|
||||
- (void)testKeysAlignWithPropertyMap {
|
||||
NSArray *mapKeys = self.cfi.propertyMap.allKeys;
|
||||
NSArray *fileInfokeys = [SNTCommandFileInfo fileInfoKeys];
|
||||
for (NSString *key in fileInfokeys) XCTAssertTrue([mapKeys containsObject:key]);
|
||||
for (NSString *key in mapKeys) XCTAssertTrue([fileInfokeys containsObject:key]);
|
||||
for (NSString *key in fileInfokeys)
|
||||
XCTAssertTrue([mapKeys containsObject:key]);
|
||||
for (NSString *key in mapKeys)
|
||||
XCTAssertTrue([fileInfokeys containsObject:key]);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedNo {
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSUnsigned userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), @"No");
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureFailed {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedStaticCodeChanged {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSStaticCodeChanged userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureNotVerifiable {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureNotVerifiable userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureUnsupported {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureUnsupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceDirectoryFailed {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceDirectoryFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceNotSupported {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceNotSupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceRulesInvalid {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceRulesInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesInvalid {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesNotFound {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotFound userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesNotSealed {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotSealed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqFailed {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqInvalid {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqUnsupported {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqUnsupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedInfoPlistFailed {
|
||||
NSString *expected = @"Yes, but can't validate as Info.plist is missing";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSInfoPlistFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedDefault {
|
||||
NSString *expected = @"Yes, but failed to validate (999)";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:999 userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:err]])
|
||||
.andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@interface SNTCommandFlushCache : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandFlushCache : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandFlushCache
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
@@ -27,7 +26,7 @@
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@interface SNTCommandRule : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandRule : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandRule
|
||||
@@ -75,8 +74,7 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
// DEBUG builds add a --force flag to allow manually adding/removing rules during testing.
|
||||
#ifdef DEBUG
|
||||
if ([config syncBaseURL] &&
|
||||
![arguments containsObject:@"--check"] &&
|
||||
if ([config syncBaseURL] && ![arguments containsObject:@"--check"] &&
|
||||
![arguments containsObject:@"--force"]) {
|
||||
#else
|
||||
if ([config syncBaseURL] && ![arguments containsObject:@"--check"]) {
|
||||
@@ -96,13 +94,13 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
for (NSUInteger i = 0; i < arguments.count; ++i) {
|
||||
NSString *arg = arguments[i];
|
||||
|
||||
if ([arg caseInsensitiveCompare:@"--allowlist"] == NSOrderedSame ||
|
||||
if ([arg caseInsensitiveCompare:@"--allow"] == NSOrderedSame ||
|
||||
[arg caseInsensitiveCompare:@"--whitelist"] == NSOrderedSame) {
|
||||
newRule.state = SNTRuleStateAllow;
|
||||
} else if ([arg caseInsensitiveCompare:@"--blocklist"] == NSOrderedSame ||
|
||||
} else if ([arg caseInsensitiveCompare:@"--block"] == NSOrderedSame ||
|
||||
[arg caseInsensitiveCompare:@"--blacklist"] == NSOrderedSame) {
|
||||
newRule.state = SNTRuleStateBlock;
|
||||
} else if ([arg caseInsensitiveCompare:@"--silent-blocklist"] == NSOrderedSame ||
|
||||
} else if ([arg caseInsensitiveCompare:@"--silent-block"] == NSOrderedSame ||
|
||||
[arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) {
|
||||
newRule.state = SNTRuleStateSilentBlock;
|
||||
} else if ([arg caseInsensitiveCompare:@"--compiler"] == NSOrderedSame) {
|
||||
@@ -165,22 +163,24 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
|
||||
}
|
||||
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (error) {
|
||||
printf("Failed to modify rules: %s", [error.localizedDescription UTF8String]);
|
||||
LOGD(@"Failure reason: %@", error.localizedFailureReason);
|
||||
exit(1);
|
||||
} else {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
}];
|
||||
[[self.daemonConn remoteObjectProxy]
|
||||
databaseRuleAddRules:@[ newRule ]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (error) {
|
||||
printf("Failed to modify rules: %s",
|
||||
[error.localizedDescription UTF8String]);
|
||||
LOGD(@"Failure reason: %@", error.localizedFailureReason);
|
||||
exit(1);
|
||||
} else {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)daemonConn {
|
||||
@@ -193,63 +193,67 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
fileSHA256:fileSHA256
|
||||
certificateSHA256:certificateSHA256
|
||||
reply:^(SNTEventState s) {
|
||||
output = (SNTEventStateAllow & s) ? @"Allowed".mutableCopy : @"Blocked".mutableCopy;
|
||||
switch (s) {
|
||||
case SNTEventStateAllowUnknown:
|
||||
case SNTEventStateBlockUnknown:
|
||||
[output appendString:@" (Unknown)"];
|
||||
break;
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateBlockBinary:
|
||||
[output appendString:@" (Binary)"];
|
||||
break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateBlockCertificate:
|
||||
[output appendString:@" (Certificate)"];
|
||||
break;
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateBlockScope:
|
||||
[output appendString:@" (Scope)"];
|
||||
break;
|
||||
case SNTEventStateAllowCompiler:
|
||||
[output appendString:@" (Compiler)"];
|
||||
break;
|
||||
case SNTEventStateAllowTransitive:
|
||||
[output appendString:@" (Transitive)"];
|
||||
break;
|
||||
default:
|
||||
output = @"None".mutableCopy;
|
||||
break;
|
||||
}
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
if ((SNTEventStateAllow & s)) {
|
||||
[output insertString:@"\033[32m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
} else if ((SNTEventStateBlock & s)) {
|
||||
[output insertString:@"\033[31m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
} else {
|
||||
[output insertString:@"\033[33m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
output = (SNTEventStateAllow & s)
|
||||
? @"Allowed".mutableCopy
|
||||
: @"Blocked".mutableCopy;
|
||||
switch (s) {
|
||||
case SNTEventStateAllowUnknown:
|
||||
case SNTEventStateBlockUnknown:
|
||||
[output appendString:@" (Unknown)"];
|
||||
break;
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateBlockBinary:
|
||||
[output appendString:@" (Binary)"];
|
||||
break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateBlockCertificate:
|
||||
[output appendString:@" (Certificate)"];
|
||||
break;
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateBlockScope:
|
||||
[output appendString:@" (Scope)"];
|
||||
break;
|
||||
case SNTEventStateAllowCompiler:
|
||||
[output appendString:@" (Compiler)"];
|
||||
break;
|
||||
case SNTEventStateAllowTransitive:
|
||||
[output appendString:@" (Transitive)"];
|
||||
break;
|
||||
default: output = @"None".mutableCopy; break;
|
||||
}
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
if ((SNTEventStateAllow & s)) {
|
||||
[output insertString:@"\033[32m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
} else if ((SNTEventStateBlock & s)) {
|
||||
[output insertString:@"\033[31m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
} else {
|
||||
[output insertString:@"\033[33m" atIndex:0];
|
||||
[output appendString:@"\033[0m"];
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
printf("Cannot communicate with daemon");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] databaseRuleForBinarySHA256:fileSHA256
|
||||
certificateSHA256:certificateSHA256
|
||||
reply:^(SNTRule *r) {
|
||||
if (r.state == SNTRuleStateAllowTransitive) {
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:r.timestamp];
|
||||
[output appendString:[NSString stringWithFormat:@"\nlast access date: %@", [date description]]];
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
[[daemonConn remoteObjectProxy]
|
||||
databaseRuleForBinarySHA256:fileSHA256
|
||||
certificateSHA256:certificateSHA256
|
||||
reply:^(SNTRule *r) {
|
||||
if (r.state == SNTRuleStateAllowTransitive) {
|
||||
NSDate *date =
|
||||
[NSDate dateWithTimeIntervalSinceReferenceDate:r.timestamp];
|
||||
[output
|
||||
appendString:[NSString stringWithFormat:@"\nlast access date: %@",
|
||||
[date description]]];
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
printf("Cannot communicate with daemon");
|
||||
exit(1);
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@interface SNTCommandStatus : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandStatus : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandStatus
|
||||
@@ -60,15 +60,9 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case SNTClientModeMonitor:
|
||||
clientMode = @"Monitor";
|
||||
break;
|
||||
case SNTClientModeLockdown:
|
||||
clientMode = @"Lockdown";
|
||||
break;
|
||||
default:
|
||||
clientMode = [NSString stringWithFormat:@"Unknown (%ld)", cm];
|
||||
break;
|
||||
case SNTClientModeMonitor: clientMode = @"Monitor"; break;
|
||||
case SNTClientModeLockdown: clientMode = @"Lockdown"; break;
|
||||
default: clientMode = [NSString stringWithFormat:@"Unknown (%ld)", cm]; break;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
@@ -86,9 +80,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;
|
||||
@@ -101,10 +97,8 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
|
||||
__block int64_t compilerRuleCount = -1, transitiveRuleCount = -1;
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary,
|
||||
int64_t certificate,
|
||||
int64_t compiler,
|
||||
int64_t transitive) {
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate,
|
||||
int64_t compiler, int64_t transitive) {
|
||||
binaryRuleCount = binary;
|
||||
certRuleCount = certificate;
|
||||
compilerRuleCount = compiler;
|
||||
@@ -174,7 +168,7 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
NSString *fullSyncLastSuccessStr = [dateFormatter stringFromDate:fullSyncLastSuccess] ?: @"Never";
|
||||
NSString *ruleSyncLastSuccessStr =
|
||||
[dateFormatter stringFromDate:ruleSyncLastSuccess] ?: fullSyncLastSuccessStr;
|
||||
[dateFormatter stringFromDate:ruleSyncLastSuccess] ?: fullSyncLastSuccessStr;
|
||||
|
||||
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
|
||||
|
||||
@@ -206,10 +200,10 @@ 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),
|
||||
@"non_root_cache_count" : @(nonRootCacheCount),
|
||||
};
|
||||
}
|
||||
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
|
||||
@@ -224,8 +218,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);
|
||||
}
|
||||
@@ -245,8 +239,7 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf(" %-25s | %s\n", "Push Notifications",
|
||||
(pushNotifications ? "Connected" : "Disconnected"));
|
||||
printf(" %-25s | %s\n", "Bundle Scanning", (enableBundles ? "Yes" : "No"));
|
||||
printf(" %-25s | %s\n", "Transitive Rules",
|
||||
(enableTransitiveRules? "Yes" : "No"));
|
||||
printf(" %-25s | %s\n", "Transitive Rules", (enableTransitiveRules ? "Yes" : "No"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@interface SNTCommandVersion : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandVersion : SNTCommand <SNTCommandProtocol>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandVersion
|
||||
@@ -75,9 +75,8 @@ REGISTER_COMMAND_NAME(@"version")
|
||||
return @"un-needed (SystemExtension being used)";
|
||||
}
|
||||
|
||||
NSDictionary *loadedKexts = CFBridgingRelease(
|
||||
KextManagerCopyLoadedKextInfo((__bridge CFArrayRef) @[ @(USERCLIENT_ID) ],
|
||||
(__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
|
||||
NSDictionary *loadedKexts = CFBridgingRelease(KextManagerCopyLoadedKextInfo(
|
||||
(__bridge CFArrayRef) @[ @(USERCLIENT_ID) ], (__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
|
||||
|
||||
if (loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"]) {
|
||||
return loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"];
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
windowSize += 16;
|
||||
}
|
||||
|
||||
if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED, windowSize, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
|
||||
if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowSize, 8,
|
||||
Z_DEFAULT_STRATEGY) == Z_OK) {
|
||||
NSMutableData *data = [NSMutableData dataWithLength:chunkSize];
|
||||
while (stream.avail_out == 0) {
|
||||
if (stream.total_out >= [data length]) {
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
#import "Source/common/SNTDropRootPrivs.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
|
||||
|
||||
@interface SNTCommandSync : SNTCommand<SNTCommandProtocol>
|
||||
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol>
|
||||
@property MOLXPCConnection *listener;
|
||||
@property SNTCommandSyncManager *syncManager;
|
||||
@end
|
||||
|
||||
@@ -37,6 +37,7 @@ extern NSString *const kBinaryRuleCount;
|
||||
extern NSString *const kCertificateRuleCount;
|
||||
extern NSString *const kCompilerRuleCount;
|
||||
extern NSString *const kTransitiveRuleCount;
|
||||
extern NSString *const kFullSyncInterval;
|
||||
extern NSString *const kFCMToken;
|
||||
extern NSString *const kFCMFullSyncInterval;
|
||||
extern NSString *const kFCMGlobalRuleSyncDeadline;
|
||||
@@ -127,4 +128,3 @@ extern const NSUInteger kDefaultEventBatchSize;
|
||||
extern const NSUInteger kDefaultFullSyncInterval;
|
||||
extern const NSUInteger kDefaultFCMFullSyncInterval;
|
||||
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ NSString *const kBinaryRuleCount = @"binary_rule_count";
|
||||
NSString *const kCertificateRuleCount = @"certificate_rule_count";
|
||||
NSString *const kCompilerRuleCount = @"compiler_rule_count";
|
||||
NSString *const kTransitiveRuleCount = @"transitive_rule_count";
|
||||
NSString *const kFullSyncInterval = @"full_sync_interval";
|
||||
NSString *const kFCMToken = @"fcm_token";
|
||||
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
|
||||
NSString *const kFCMGlobalRuleSyncDeadline = @"fcm_global_rule_sync_deadline";
|
||||
|
||||
@@ -52,12 +52,12 @@
|
||||
if (event.idx) [eventIds addObject:event.idx];
|
||||
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
|
||||
}
|
||||
|
||||
|
||||
if (!self.syncState.cleanSync) {
|
||||
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
|
||||
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{kEvents : uploadEvents}]];
|
||||
if (!r) return NO;
|
||||
|
||||
// A list of bundle hashes that require their related binary events to be uploaded.
|
||||
// A list of bundle hashes that require their related binary events to be uploaded.
|
||||
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
|
||||
|
||||
LOGI(@"Uploaded %lu events", uploadEvents.count);
|
||||
@@ -79,7 +79,8 @@
|
||||
- (NSDictionary *)dictionaryForEvent:(SNTStoredEvent *)event {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
#define ADDKEY(dict, key, value) if (value) dict[key] = value
|
||||
#define ADDKEY(dict, key, value) \
|
||||
if (value) dict[key] = value
|
||||
NSMutableDictionary *newEvent = [NSMutableDictionary dictionary];
|
||||
|
||||
ADDKEY(newEvent, kFileSHA256, event.fileSHA256);
|
||||
|
||||
115
Source/santactl/Commands/sync/SNTCommandSyncFCM.h
Normal file
115
Source/santactl/Commands/sync/SNTCommandSyncFCM.h
Normal 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
|
||||
479
Source/santactl/Commands/sync/SNTCommandSyncFCM.m
Normal file
479
Source/santactl/Commands/sync/SNTCommandSyncFCM.m
Normal file
@@ -0,0 +1,479 @@
|
||||
/// 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.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, _connectComponents.host.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.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
|
||||
@@ -21,7 +21,7 @@
|
||||
///
|
||||
/// Handles push notifications and periodic syncing with a sync server.
|
||||
///
|
||||
@interface SNTCommandSyncManager : NSObject<SNTSyncdXPC>
|
||||
@interface SNTCommandSyncManager : NSObject <SNTSyncdXPC>
|
||||
|
||||
@property(readonly, nonatomic) BOOL daemon;
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -55,11 +55,14 @@ static NSString *const kFCMTargetHostIDKey = @"target_host_id";
|
||||
// allowlistNotificationQueue is used to serialize access to the allowlistNotifications dictionary.
|
||||
@property(nonatomic) NSOperationQueue *allowlistNotificationQueue;
|
||||
|
||||
@property NSUInteger fullSyncInterval;
|
||||
|
||||
@property NSUInteger FCMFullSyncInterval;
|
||||
@property NSUInteger FCMGlobalRuleSyncDeadline;
|
||||
@property NSUInteger eventBatchSize;
|
||||
|
||||
@property MOLFCMClient *FCMClient;
|
||||
@property SNTCommandSyncFCM *FCMClient;
|
||||
@property NSString *FCMToken;
|
||||
|
||||
@property(nonatomic) MOLXPCConnection *daemonConn;
|
||||
|
||||
@@ -70,8 +73,8 @@ static NSString *const kFCMTargetHostIDKey = @"target_host_id";
|
||||
@end
|
||||
|
||||
// Called when the network state changes
|
||||
static void reachabilityHandler(
|
||||
SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
|
||||
static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
|
||||
void *info) {
|
||||
// Put this check and set on the main thread to ensure serial access.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SNTCommandSyncManager *commandSyncManager = (__bridge SNTCommandSyncManager *)info;
|
||||
@@ -100,8 +103,8 @@ static void reachabilityHandler(
|
||||
[self unlockAction:kFullSync];
|
||||
}];
|
||||
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
|
||||
dispatch_source_set_timer(self.ruleSyncTimer,
|
||||
DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
|
||||
dispatch_source_set_timer(self.ruleSyncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER,
|
||||
0);
|
||||
if (![[SNTConfigurator configurator] syncBaseURL]) return;
|
||||
[self lockAction:kRuleSync];
|
||||
SNTCommandSyncState *syncState = [self createSyncState];
|
||||
@@ -122,6 +125,7 @@ static void reachabilityHandler(
|
||||
_allowlistNotificationQueue = [[NSOperationQueue alloc] init];
|
||||
_allowlistNotificationQueue.maxConcurrentOperationCount = 1; // make this a serial queue
|
||||
|
||||
_fullSyncInterval = kDefaultFullSyncInterval;
|
||||
_eventBatchSize = kDefaultEventBatchSize;
|
||||
_FCMFullSyncInterval = kDefaultFCMFullSyncInterval;
|
||||
_FCMGlobalRuleSyncDeadline = kDefaultFCMGlobalRuleSyncDeadline;
|
||||
@@ -144,7 +148,7 @@ static void reachabilityHandler(
|
||||
LOGD(@"Events upload complete");
|
||||
} else {
|
||||
LOGE(@"Events upload failed. Will retry again once %@ is reachable",
|
||||
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
|
||||
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
|
||||
[self startReachability];
|
||||
}
|
||||
}
|
||||
@@ -157,7 +161,7 @@ static void reachabilityHandler(
|
||||
}
|
||||
SNTCommandSyncState *syncState = [self createSyncState];
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
|
||||
if ([p uploadEvents:@[event]]) {
|
||||
if ([p uploadEvents:@[ event ]]) {
|
||||
if ([syncState.bundleBinaryRequests containsObject:event.fileBundleHash]) {
|
||||
reply(SNTBundleEventActionSendEvents);
|
||||
LOGD(@"Needs related events");
|
||||
@@ -170,7 +174,7 @@ static void reachabilityHandler(
|
||||
// wanted them or not. If they weren't needed the server will simply ignore them.
|
||||
reply(SNTBundleEventActionStoreEvents);
|
||||
LOGE(@"Bundle event upload failed. Will retry again once %@ is reachable",
|
||||
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
|
||||
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
|
||||
[self startReachability];
|
||||
}
|
||||
}
|
||||
@@ -182,26 +186,36 @@ 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;
|
||||
STRONGIFY(self);
|
||||
LOGD(@"%@", message);
|
||||
[self.FCMClient acknowledgeMessage:message];
|
||||
[self processFCMMessage:message withMachineID:machineID];
|
||||
}];
|
||||
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);
|
||||
@@ -209,13 +223,10 @@ static void reachabilityHandler(
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -244,7 +255,7 @@ static void reachabilityHandler(
|
||||
NSString *fileName = message[kFCMFileNameKey];
|
||||
if (fileName && fileHash) {
|
||||
[self.allowlistNotificationQueue addOperationWithBlock:^{
|
||||
self.allowlistNotifications[fileHash] = @{ kFileName : fileName }.mutableCopy;
|
||||
self.allowlistNotifications[fileHash] = @{kFileName : fileName}.mutableCopy;
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -279,6 +290,10 @@ static void reachabilityHandler(
|
||||
}
|
||||
|
||||
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
|
||||
if (!messageData) {
|
||||
LOGD(@"Unable to parse push notification message data");
|
||||
return nil;
|
||||
}
|
||||
NSError *error;
|
||||
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
|
||||
options:0
|
||||
@@ -334,6 +349,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]) {
|
||||
@@ -345,17 +365,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.", kDefaultFullSyncInterval / 60);
|
||||
LOGD(@"FCM not enabled. Sync every %lu min.", syncState.fullSyncInterval / 60);
|
||||
[self.FCMClient disconnect];
|
||||
self.FCMClient = nil;
|
||||
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
|
||||
self.fullSyncInterval = syncState.fullSyncInterval;
|
||||
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.fullSyncInterval];
|
||||
}
|
||||
|
||||
if (preflightOnly) return;
|
||||
return [self eventUploadWithSyncState:syncState];
|
||||
} else {
|
||||
if (!syncState.daemon) {
|
||||
@@ -369,6 +391,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");
|
||||
@@ -380,6 +403,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");
|
||||
@@ -391,6 +415,7 @@ static void reachabilityHandler(
|
||||
}
|
||||
|
||||
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
|
||||
LOGD(@"Postflight starting");
|
||||
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Postflight complete");
|
||||
@@ -405,9 +430,9 @@ static void reachabilityHandler(
|
||||
#pragma mark internal helpers
|
||||
|
||||
- (dispatch_source_t)createSyncTimerWithBlock:(void (^)(void))block {
|
||||
dispatch_source_t timerQueue = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
dispatch_source_t timerQueue =
|
||||
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
dispatch_source_set_event_handler(timerQueue, block);
|
||||
dispatch_resume(timerQueue);
|
||||
return timerQueue;
|
||||
@@ -478,6 +503,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;
|
||||
}
|
||||
@@ -515,8 +545,8 @@ static void reachabilityHandler(
|
||||
.release = (void (*)(const void *))CFBridgingRelease,
|
||||
};
|
||||
if (SCNetworkReachabilitySetCallback(self->_reachability, reachabilityHandler, &context)) {
|
||||
SCNetworkReachabilitySetDispatchQueue(self->_reachability,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
SCNetworkReachabilitySetDispatchQueue(
|
||||
self->_reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
} else {
|
||||
[self stopReachability];
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
// Set client mode if it changed
|
||||
if (self.syncState.clientMode) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] setClientMode:self.syncState.clientMode
|
||||
reply:replyBlock];
|
||||
[[self.daemonConn remoteObjectProxy] setClientMode:self.syncState.clientMode reply:replyBlock];
|
||||
}
|
||||
|
||||
// Remove clean sync flag if we did a clean sync
|
||||
|
||||
@@ -39,13 +39,12 @@
|
||||
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);
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary,
|
||||
int64_t certificate,
|
||||
int64_t compiler,
|
||||
int64_t transitive) {
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate,
|
||||
int64_t compiler, int64_t transitive) {
|
||||
requestDict[kBinaryRuleCount] = @(binary);
|
||||
requestDict[kCertificateRuleCount] = @(certificate);
|
||||
requestDict[kCompilerRuleCount] = @(compiler);
|
||||
@@ -56,10 +55,8 @@
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case SNTClientModeMonitor:
|
||||
requestDict[kClientMode] = kClientModeMonitor; break;
|
||||
case SNTClientModeLockdown:
|
||||
requestDict[kClientMode] = kClientModeLockdown; break;
|
||||
case SNTClientModeMonitor: requestDict[kClientMode] = kClientModeMonitor; break;
|
||||
case SNTClientModeLockdown: requestDict[kClientMode] = kClientModeLockdown; break;
|
||||
default: break;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
@@ -88,29 +85,34 @@
|
||||
dispatch_group_enter(group);
|
||||
NSNumber *enableBundles = resp[kEnableBundles];
|
||||
if (!enableBundles) enableBundles = resp[kEnableBundlesDeprecated];
|
||||
[[self.daemonConn remoteObjectProxy] setEnableBundles:[enableBundles boolValue] reply:^{
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
[[self.daemonConn remoteObjectProxy] setEnableBundles:[enableBundles boolValue]
|
||||
reply:^{
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
dispatch_group_enter(group);
|
||||
NSNumber *enableTransitiveRules = resp[kEnableTransitiveRules];
|
||||
if (!enableTransitiveRules) enableTransitiveRules = resp[kEnableTransitiveRulesDeprecated];
|
||||
if (!enableTransitiveRules) enableTransitiveRules = resp[kEnableTransitiveRulesSuperDeprecated];
|
||||
BOOL enabled = [enableTransitiveRules boolValue];
|
||||
[[self.daemonConn remoteObjectProxy] setEnableTransitiveRules:enabled reply:^{
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
[[self.daemonConn remoteObjectProxy] setEnableTransitiveRules:enabled
|
||||
reply:^{
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
self.syncState.eventBatchSize = [resp[kBatchSize] unsignedIntegerValue] ?: kDefaultEventBatchSize;
|
||||
self.syncState.FCMToken = resp[kFCMToken];
|
||||
|
||||
// Don't let these go too low
|
||||
NSUInteger value = [resp[kFCMFullSyncInterval] unsignedIntegerValue];
|
||||
NSUInteger FCMIntervalValue = [resp[kFCMFullSyncInterval] unsignedIntegerValue];
|
||||
self.syncState.FCMFullSyncInterval =
|
||||
(value < kDefaultFullSyncInterval) ? kDefaultFCMFullSyncInterval : value;
|
||||
value = [resp[kFCMGlobalRuleSyncDeadline] unsignedIntegerValue];
|
||||
(FCMIntervalValue < kDefaultFullSyncInterval) ? kDefaultFCMFullSyncInterval : FCMIntervalValue;
|
||||
FCMIntervalValue = [resp[kFCMGlobalRuleSyncDeadline] unsignedIntegerValue];
|
||||
self.syncState.FCMGlobalRuleSyncDeadline =
|
||||
(value < 60) ? kDefaultFCMGlobalRuleSyncDeadline : value;
|
||||
(FCMIntervalValue < 60) ? kDefaultFCMGlobalRuleSyncDeadline : FCMIntervalValue;
|
||||
|
||||
// Check if our sync interval has changed
|
||||
NSUInteger intervalValue = [resp[kFullSyncInterval] unsignedIntegerValue];
|
||||
self.syncState.fullSyncInterval = (intervalValue < 60) ? kDefaultFullSyncInterval : intervalValue;
|
||||
|
||||
if ([resp[kClientMode] isEqual:kClientModeMonitor]) {
|
||||
self.syncState.clientMode = SNTClientModeMonitor;
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleAddRules:newRules
|
||||
cleanSlate:self.syncState.cleanSync
|
||||
reply:^(NSError *e) {
|
||||
error = e;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
error = e;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
|
||||
|
||||
if (error) {
|
||||
@@ -55,9 +55,10 @@
|
||||
|
||||
// Tell santad to record a successful rules sync and wait for it to finish.
|
||||
sema = dispatch_semaphore_create(0);
|
||||
[[self.daemonConn remoteObjectProxy] setRuleSyncLastSuccess:[NSDate date] reply:^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
[[self.daemonConn remoteObjectProxy] setRuleSyncLastSuccess:[NSDate date]
|
||||
reply:^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
|
||||
|
||||
LOGI(@"Processed %lu rules", newRules.count);
|
||||
@@ -118,8 +119,9 @@
|
||||
if (remaining && [remaining intValue] == 0) {
|
||||
[processed addObject:key];
|
||||
NSString *message = [NSString stringWithFormat:@"%@ can now be run", notifier[kFileName]];
|
||||
[[self.daemonConn remoteObjectProxy]
|
||||
postRuleSyncNotificationWithCustomMessage:message reply:^{}];
|
||||
[[self.daemonConn remoteObjectProxy] postRuleSyncNotificationWithCustomMessage:message
|
||||
reply:^{
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +129,6 @@
|
||||
[self.syncState.allowlistNotifications removeObjectsForKeys:processed];
|
||||
}
|
||||
|
||||
|
||||
// Converts rule information downloaded from the server into a SNTRule. Because any information
|
||||
// not recorded by SNTRule is thrown away here, this method is also responsible for dealing with
|
||||
// the extra bundle rule information (bundle_hash & rule_count).
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
|
||||
@param state A holder for state used across requests
|
||||
*/
|
||||
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)state NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)state
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
|
||||
@@ -44,11 +44,13 @@
|
||||
}
|
||||
|
||||
- (BOOL)sync {
|
||||
[self doesNotRecognizeSelector:_cmd]; __builtin_unreachable();
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
- (NSString *)stageURL {
|
||||
[self doesNotRecognizeSelector:_cmd]; __builtin_unreachable();
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
- (NSMutableURLRequest *)requestWithDictionary:(NSDictionary *)dictionary {
|
||||
@@ -70,7 +72,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 +85,32 @@
|
||||
- (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;
|
||||
@@ -145,17 +159,16 @@
|
||||
__block NSError *_error;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
NSURLSessionDataTask *task = [self.urlSession dataTaskWithRequest:request
|
||||
completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
_response = (NSHTTPURLResponse *)response;
|
||||
}
|
||||
_data = data;
|
||||
_error = error;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
NSURLSessionDataTask *task =
|
||||
[self.urlSession dataTaskWithRequest:request
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
_response = (NSHTTPURLResponse *)response;
|
||||
}
|
||||
_data = data;
|
||||
_error = error;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
[task resume];
|
||||
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * timeout))) {
|
||||
@@ -168,7 +181,7 @@
|
||||
}
|
||||
|
||||
- (NSData *)stripXssi:(NSData *)data {
|
||||
static const char xssi[3] = { ']', ')', '}' };
|
||||
static const char xssi[3] = {']', ')', '}'};
|
||||
if (data.length < 3 || strncmp(data.bytes, xssi, 3)) return data;
|
||||
return [data subdataWithRange:NSMakeRange(3, data.length - 3)];
|
||||
}
|
||||
@@ -185,7 +198,9 @@
|
||||
[self performRequest:request timeout:10 response:&response error:NULL];
|
||||
if (response.statusCode == 200) {
|
||||
NSDictionary *headers = [response allHeaderFields];
|
||||
[[self.daemonConn remoteObjectProxy] setXsrfToken:headers[kXSRFToken] reply:^{}];
|
||||
[[self.daemonConn remoteObjectProxy] setXsrfToken:headers[kXSRFToken]
|
||||
reply:^{
|
||||
}];
|
||||
self.syncState.xsrfToken = headers[kXSRFToken];
|
||||
LOGD(@"Retrieved new XSRF token");
|
||||
success = YES;
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
/// An XSRF token to send in the headers with each request.
|
||||
@property NSString *xsrfToken;
|
||||
|
||||
/// Full sync interval in seconds without FCM to update kDefaultFullSyncInterval => when FCM
|
||||
/// is not used, defaults to 10m.
|
||||
@property NSUInteger fullSyncInterval;
|
||||
|
||||
/// An FCM token to subscribe to push notifications.
|
||||
@property(copy) NSString *FCMToken;
|
||||
|
||||
@@ -74,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
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncEventUpload.h"
|
||||
#import "SNTCommandSyncPostflight.h"
|
||||
#import "SNTCommandSyncPreflight.h"
|
||||
#import "SNTCommandSyncRuleDownload.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTCommandSyncStage.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
@@ -75,7 +75,7 @@
|
||||
- (void)stubRequestBody:(NSData *)respData
|
||||
response:(NSURLResponse *)resp
|
||||
error:(NSError *)err
|
||||
validateBlock:(BOOL(^)(NSURLRequest *req))validateBlock {
|
||||
validateBlock:(BOOL (^)(NSURLRequest *req))validateBlock {
|
||||
if (!respData) respData = (NSData *)[NSNull null];
|
||||
if (!resp) resp = [self responseWithCode:200 headerDict:nil];
|
||||
if (!err) err = (NSError *)[NSNull null];
|
||||
@@ -87,9 +87,9 @@
|
||||
return validateBlock(req);
|
||||
};
|
||||
|
||||
OCMStub([self.syncState.session dataTaskWithRequest:[OCMArg checkWithBlock:validateBlockWrapper]
|
||||
completionHandler:([OCMArg invokeBlockWithArgs:respData,
|
||||
resp, err, nil])]);
|
||||
OCMStub([self.syncState.session
|
||||
dataTaskWithRequest:[OCMArg checkWithBlock:validateBlockWrapper]
|
||||
completionHandler:([OCMArg invokeBlockWithArgs:respData, resp, err, nil])]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,22 +147,31 @@
|
||||
|
||||
// Stub initial failing request
|
||||
NSURLResponse *resp = [self responseWithCode:403 headerDict:nil];
|
||||
[self stubRequestBody:nil response:resp error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
return ([req.URL.absoluteString containsString:@"/a/"] &&
|
||||
![req valueForHTTPHeaderField:@"X-XSRF-TOKEN"]);
|
||||
}];
|
||||
[self stubRequestBody:nil
|
||||
response:resp
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
return ([req.URL.absoluteString containsString:@"/a/"] &&
|
||||
![req valueForHTTPHeaderField:@"X-XSRF-TOKEN"]);
|
||||
}];
|
||||
|
||||
// Stub XSRF token request
|
||||
resp = [self responseWithCode:200 headerDict:@{ @"X-XSRF-TOKEN": @"my-xsrf-token" }];
|
||||
[self stubRequestBody:nil response:resp error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
return [req.URL.absoluteString containsString:@"/xsrf/"];
|
||||
}];
|
||||
resp = [self responseWithCode:200 headerDict:@{@"X-XSRF-TOKEN" : @"my-xsrf-token"}];
|
||||
[self stubRequestBody:nil
|
||||
response:resp
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
return [req.URL.absoluteString containsString:@"/xsrf/"];
|
||||
}];
|
||||
|
||||
// Stub succeeding request
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
return ([req.URL.absoluteString containsString:@"/a/"] &&
|
||||
[[req valueForHTTPHeaderField:@"X-XSRF-TOKEN"] isEqual:@"my-xsrf-token"]);
|
||||
}];
|
||||
[self stubRequestBody:nil
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
return ([req.URL.absoluteString containsString:@"/a/"] &&
|
||||
[[req valueForHTTPHeaderField:@"X-XSRF-TOKEN"] isEqual:@"my-xsrf-token"]);
|
||||
}];
|
||||
|
||||
NSString *stageName = [@"a" stringByAppendingFormat:@"/%@", self.syncState.machineID];
|
||||
NSURL *u1 = [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
|
||||
@@ -192,21 +201,22 @@
|
||||
SNTCommandSyncPreflight *sut = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
|
||||
|
||||
int64_t bin = 5, cert = 8, compiler = 2, transitive = 19;
|
||||
OCMStub([self.daemonConnRop databaseRuleCounts:([OCMArg invokeBlockWithArgs:
|
||||
OCMOCK_VALUE(bin),
|
||||
OCMOCK_VALUE(cert),
|
||||
OCMOCK_VALUE(compiler),
|
||||
OCMOCK_VALUE(transitive),
|
||||
nil])]);
|
||||
OCMStub([self.daemonConnRop
|
||||
databaseRuleCounts:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(bin), OCMOCK_VALUE(cert),
|
||||
OCMOCK_VALUE(compiler),
|
||||
OCMOCK_VALUE(transitive), nil])]);
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
XCTAssertEqualObjects(requestDict[kBinaryRuleCount], @(5));
|
||||
XCTAssertEqualObjects(requestDict[kCertificateRuleCount], @(8));
|
||||
XCTAssertEqualObjects(requestDict[kCompilerRuleCount], @(2));
|
||||
XCTAssertEqualObjects(requestDict[kTransitiveRuleCount], @(19));
|
||||
return YES;
|
||||
}];
|
||||
[self stubRequestBody:nil
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
XCTAssertEqualObjects(requestDict[kBinaryRuleCount], @(5));
|
||||
XCTAssertEqualObjects(requestDict[kCertificateRuleCount], @(8));
|
||||
XCTAssertEqualObjects(requestDict[kCompilerRuleCount], @(2));
|
||||
XCTAssertEqualObjects(requestDict[kTransitiveRuleCount], @(19));
|
||||
return YES;
|
||||
}];
|
||||
|
||||
[sut sync];
|
||||
}
|
||||
@@ -218,12 +228,15 @@
|
||||
OCMStub([processInfoMock processInfo]).andReturn(processInfoMock);
|
||||
[OCMStub([processInfoMock arguments]) andReturn:@[ @"xctest", @"--clean" ]];
|
||||
|
||||
NSData *respData = [self dataFromDict:@{ kCleanSync: @YES }];
|
||||
[self stubRequestBody:respData response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
XCTAssertEqualObjects(requestDict[kRequestCleanSync], @YES);
|
||||
return YES;
|
||||
}];
|
||||
NSData *respData = [self dataFromDict:@{kCleanSync : @YES}];
|
||||
[self stubRequestBody:respData
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
XCTAssertEqualObjects(requestDict[kRequestCleanSync], @YES);
|
||||
return YES;
|
||||
}];
|
||||
|
||||
[sut sync];
|
||||
|
||||
@@ -252,47 +265,51 @@
|
||||
|
||||
OCMStub([self.daemonConnRop databaseEventsPending:([OCMArg invokeBlockWithArgs:events, nil])]);
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
[self
|
||||
stubRequestBody:nil
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertEqual(events.count, 2);
|
||||
XCTAssertEqual(events.count, 2);
|
||||
|
||||
NSDictionary *event = events[0];
|
||||
XCTAssertEqualObjects(event[kFileSHA256],
|
||||
@"ff98fa0c0a1095fedcbe4d388a9760e71399a5c3c017a847ffa545663b57929a");
|
||||
XCTAssertEqualObjects(event[kFileName], @"yes");
|
||||
XCTAssertEqualObjects(event[kFilePath], @"/usr/bin");
|
||||
XCTAssertEqualObjects(event[kDecision], @"BLOCK_BINARY");
|
||||
NSArray *sessions = @[ @"foouser@console", @"foouser@ttys000"];
|
||||
XCTAssertEqualObjects(event[kCurrentSessions], sessions);
|
||||
NSArray *users = @[ @"foouser" ];
|
||||
XCTAssertEqualObjects(event[kLoggedInUsers], users);
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"root");
|
||||
XCTAssertEqualObjects(event[kPID], @(11196));
|
||||
XCTAssertEqualObjects(event[kPPID], @(10760));
|
||||
XCTAssertEqualObjects(event[kExecutionTime], @(1464201698.537635));
|
||||
NSDictionary *event = events[0];
|
||||
XCTAssertEqualObjects(event[kFileSHA256],
|
||||
@"ff98fa0c0a1095fedcbe4d388a9760e71399a5c3c017a847ffa545663b57929a");
|
||||
XCTAssertEqualObjects(event[kFileName], @"yes");
|
||||
XCTAssertEqualObjects(event[kFilePath], @"/usr/bin");
|
||||
XCTAssertEqualObjects(event[kDecision], @"BLOCK_BINARY");
|
||||
NSArray *sessions = @[ @"foouser@console", @"foouser@ttys000" ];
|
||||
XCTAssertEqualObjects(event[kCurrentSessions], sessions);
|
||||
NSArray *users = @[ @"foouser" ];
|
||||
XCTAssertEqualObjects(event[kLoggedInUsers], users);
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"root");
|
||||
XCTAssertEqualObjects(event[kPID], @(11196));
|
||||
XCTAssertEqualObjects(event[kPPID], @(10760));
|
||||
XCTAssertEqualObjects(event[kExecutionTime], @(1464201698.537635));
|
||||
|
||||
NSArray *certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 3);
|
||||
NSArray *certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 3);
|
||||
|
||||
NSDictionary *cert = [certs firstObject];
|
||||
XCTAssertEqualObjects(cert[kCertSHA256],
|
||||
@"2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32");
|
||||
XCTAssertEqualObjects(cert[kCertCN], @"Software Signing");
|
||||
XCTAssertEqualObjects(cert[kCertOrg], @"Apple Inc.");
|
||||
XCTAssertEqualObjects(cert[kCertOU], @"Apple Software");
|
||||
XCTAssertEqualObjects(cert[kCertValidFrom], @(1365806075));
|
||||
XCTAssertEqualObjects(cert[kCertValidUntil], @(1618266875));
|
||||
NSDictionary *cert = [certs firstObject];
|
||||
XCTAssertEqualObjects(cert[kCertSHA256],
|
||||
@"2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32");
|
||||
XCTAssertEqualObjects(cert[kCertCN], @"Software Signing");
|
||||
XCTAssertEqualObjects(cert[kCertOrg], @"Apple Inc.");
|
||||
XCTAssertEqualObjects(cert[kCertOU], @"Apple Software");
|
||||
XCTAssertEqualObjects(cert[kCertValidFrom], @(1365806075));
|
||||
XCTAssertEqualObjects(cert[kCertValidUntil], @(1618266875));
|
||||
|
||||
event = events[1];
|
||||
XCTAssertEqualObjects(event[kFileName], @"hub");
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"foouser");
|
||||
certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 0);
|
||||
event = events[1];
|
||||
XCTAssertEqualObjects(event[kFileName], @"hub");
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"foouser");
|
||||
certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 0);
|
||||
|
||||
return YES;
|
||||
}];
|
||||
return YES;
|
||||
}];
|
||||
|
||||
[sut sync];
|
||||
}
|
||||
@@ -305,25 +322,29 @@
|
||||
NSArray *events = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
|
||||
OCMStub([self.daemonConnRop databaseEventsPending:([OCMArg invokeBlockWithArgs:events, nil])]);
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
[self stubRequestBody:nil
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertEqual(events.count, 1);
|
||||
XCTAssertEqual(events.count, 1);
|
||||
|
||||
NSDictionary *event = [events firstObject];
|
||||
XCTAssertEqualObjects(event[kFileBundleID], @"com.luckymarmot.Paw");
|
||||
XCTAssertEqualObjects(event[kFileBundlePath], @"/Applications/Paw.app");
|
||||
XCTAssertEqualObjects(event[kFileBundleVersion], @"2003004001");
|
||||
XCTAssertEqualObjects(event[kFileBundleShortVersionString], @"2.3.4");
|
||||
XCTAssertEqualObjects(event[kQuarantineTimestamp], @(1464204868));
|
||||
XCTAssertEqualObjects(event[kQuarantineAgentBundleID], @"com.google.Chrome");
|
||||
XCTAssertEqualObjects(event[kQuarantineDataURL],
|
||||
@"https://d3hevc2w7wq7nj.cloudfront.net/paw/Paw-2.3.4-2003004001.zip");
|
||||
XCTAssertEqualObjects(event[kQuarantineRefererURL], @"https://luckymarmot.com/paw");
|
||||
NSDictionary *event = [events firstObject];
|
||||
XCTAssertEqualObjects(event[kFileBundleID], @"com.luckymarmot.Paw");
|
||||
XCTAssertEqualObjects(event[kFileBundlePath], @"/Applications/Paw.app");
|
||||
XCTAssertEqualObjects(event[kFileBundleVersion], @"2003004001");
|
||||
XCTAssertEqualObjects(event[kFileBundleShortVersionString], @"2.3.4");
|
||||
XCTAssertEqualObjects(event[kQuarantineTimestamp], @(1464204868));
|
||||
XCTAssertEqualObjects(event[kQuarantineAgentBundleID], @"com.google.Chrome");
|
||||
XCTAssertEqualObjects(
|
||||
event[kQuarantineDataURL],
|
||||
@"https://d3hevc2w7wq7nj.cloudfront.net/paw/Paw-2.3.4-2003004001.zip");
|
||||
XCTAssertEqualObjects(event[kQuarantineRefererURL], @"https://luckymarmot.com/paw");
|
||||
|
||||
return YES;
|
||||
}];
|
||||
return YES;
|
||||
}];
|
||||
|
||||
[sut sync];
|
||||
}
|
||||
@@ -339,10 +360,13 @@
|
||||
|
||||
__block int requestCount = 0;
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
requestCount++;
|
||||
return YES;
|
||||
}];
|
||||
[self stubRequestBody:nil
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
requestCount++;
|
||||
return YES;
|
||||
}];
|
||||
|
||||
[sut sync];
|
||||
|
||||
@@ -353,39 +377,49 @@
|
||||
|
||||
- (void)testRuleDownload {
|
||||
SNTCommandSyncRuleDownload *sut =
|
||||
[[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
|
||||
[[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
|
||||
|
||||
NSData *respData = [self dataFromFixture:@"sync_ruledownload_batch1.json"];
|
||||
[self stubRequestBody:respData response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
return requestDict[@"cursor"] == nil;
|
||||
}];
|
||||
[self stubRequestBody:respData
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
return requestDict[@"cursor"] == nil;
|
||||
}];
|
||||
|
||||
respData = [self dataFromFixture:@"sync_ruledownload_batch2.json"];
|
||||
[self stubRequestBody:respData response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
return requestDict[@"cursor"] != nil;
|
||||
}];
|
||||
[self stubRequestBody:respData
|
||||
response:nil
|
||||
error:nil
|
||||
validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
return requestDict[@"cursor"] != nil;
|
||||
}];
|
||||
|
||||
// Stub out the call to invoke the block, verification of the input is later
|
||||
OCMStub([self.daemonConnRop databaseRuleAddRules:OCMOCK_ANY
|
||||
cleanSlate:NO
|
||||
reply:([OCMArg invokeBlockWithArgs:[NSNull null], nil])]);
|
||||
OCMStub([self.daemonConnRop
|
||||
databaseRuleAddRules:OCMOCK_ANY
|
||||
cleanSlate:NO
|
||||
reply:([OCMArg invokeBlockWithArgs:[NSNull null], nil])]);
|
||||
[sut sync];
|
||||
|
||||
NSArray *rules = @[
|
||||
[[SNTRule alloc] initWithShasum:@"ee382e199f7eda58863a93a7854b930ade35798bc6856ee8e6ab6ce9277f0eab"
|
||||
state:SNTRuleStateBlock
|
||||
type:SNTRuleTypeBinary
|
||||
customMsg:@""],
|
||||
[[SNTRule alloc] initWithShasum:@"46f8c706d0533a54554af5fc163eea704f10c08b30f8a5db12bfdc04fb382fc3"
|
||||
state:SNTRuleStateAllow
|
||||
type:SNTRuleTypeCertificate
|
||||
customMsg:@""],
|
||||
[[SNTRule alloc] initWithShasum:@"7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142"
|
||||
state:SNTRuleStateBlock
|
||||
type:SNTRuleTypeCertificate
|
||||
customMsg:@"Hi There"],
|
||||
[[SNTRule alloc]
|
||||
initWithShasum:@"ee382e199f7eda58863a93a7854b930ade35798bc6856ee8e6ab6ce9277f0eab"
|
||||
state:SNTRuleStateBlock
|
||||
type:SNTRuleTypeBinary
|
||||
customMsg:@""],
|
||||
[[SNTRule alloc]
|
||||
initWithShasum:@"46f8c706d0533a54554af5fc163eea704f10c08b30f8a5db12bfdc04fb382fc3"
|
||||
state:SNTRuleStateAllow
|
||||
type:SNTRuleTypeCertificate
|
||||
customMsg:@""],
|
||||
[[SNTRule alloc]
|
||||
initWithShasum:@"7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142"
|
||||
state:SNTRuleStateBlock
|
||||
type:SNTRuleTypeCertificate
|
||||
customMsg:@"Hi There"],
|
||||
];
|
||||
|
||||
OCMVerify([self.daemonConnRop databaseRuleAddRules:rules cleanSlate:NO reply:OCMOCK_ANY]);
|
||||
|
||||
@@ -54,9 +54,9 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTCommand : NSObject<SNTCommandRunProtocol>
|
||||
@interface SNTCommand : NSObject <SNTCommandRunProtocol>
|
||||
|
||||
@property(nonatomic,readonly) MOLXPCConnection *daemonConn;
|
||||
@property(nonatomic, readonly) MOLXPCConnection *daemonConn;
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn;
|
||||
@@ -65,4 +65,3 @@
|
||||
|
||||
- (void)printErrorUsageAndExit:(NSString *)error;
|
||||
@end
|
||||
|
||||
|
||||
@@ -62,5 +62,7 @@
|
||||
/// NSString). Must be placed just inside the implementation of the class, ideally at the top.
|
||||
/// The class that uses this macro must implement the @c SNTCommand protcol.
|
||||
///
|
||||
#define REGISTER_COMMAND_NAME(a) \
|
||||
+ (void)load { [SNTCommandController registerCommand:[self class] named:a]; }
|
||||
#define REGISTER_COMMAND_NAME(a) \
|
||||
+(void)load { \
|
||||
[SNTCommandController registerCommand:[self class] named:a]; \
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ static NSMutableDictionary *registeredCommands;
|
||||
for (NSString *cmdName in
|
||||
[[registeredCommands allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]) {
|
||||
Class<SNTCommandProtocol> command = registeredCommands[cmdName];
|
||||
[helpText appendFormat:@"\t%*s - %@\n", longestCommandName,
|
||||
[cmdName UTF8String], [command shortHelpText]];
|
||||
[helpText appendFormat:@"\t%*s - %@\n", longestCommandName, [cmdName UTF8String],
|
||||
[command shortHelpText]];
|
||||
}
|
||||
|
||||
[helpText appendFormat:@"\nSee 'santactl help <command>' to read about a specific subcommand."];
|
||||
|
||||
@@ -45,16 +45,14 @@ int main(int argc, const char *argv[]) {
|
||||
[arguments removeObjectAtIndex:0];
|
||||
|
||||
NSString *commandName = [arguments firstObject];
|
||||
if (!commandName ||
|
||||
[commandName isEqualToString:@"usage"] ||
|
||||
if (!commandName || [commandName isEqualToString:@"usage"] ||
|
||||
[commandName isEqualToString:@"commands"]) {
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
[arguments removeObjectAtIndex:0];
|
||||
|
||||
if ([commandName isEqualToString:@"help"] ||
|
||||
[commandName isEqualToString:@"-h"] ||
|
||||
if ([commandName isEqualToString:@"help"] || [commandName isEqualToString:@"-h"] ||
|
||||
[commandName isEqualToString:@"--help"]) {
|
||||
if ([arguments count]) {
|
||||
// User wants help for specific command
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user