mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
352 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ff9cb34490 | ||
|
|
60405f1e10 | ||
|
|
ac9d3b2adf | ||
|
|
7e8bd46da3 | ||
|
|
2f6ed455e5 | ||
|
|
8cb86b6d1d | ||
|
|
fc074f6014 | ||
|
|
a7856e60e8 | ||
|
|
41a40c9fbd | ||
|
|
8c18f6ebf5 | ||
|
|
949053fedd | ||
|
|
8d2c39b71d | ||
|
|
8f872fb4fc | ||
|
|
5512f8cf19 | ||
|
|
6742b38e31 | ||
|
|
d1635f7e11 | ||
|
|
e2b865c081 | ||
|
|
012b02de5d | ||
|
|
11ebead617 | ||
|
|
e3fbabfe37 | ||
|
|
8757da7822 | ||
|
|
428582f471 | ||
|
|
6e0effc0f4 | ||
|
|
683114fbec | ||
|
|
d9ebb4e3db | ||
|
|
e6aaf2f198 | ||
|
|
1c3757d4ab | ||
|
|
4346bb29c2 | ||
|
|
09655df8fc | ||
|
|
7504cd36e1 | ||
|
|
cafef66933 | ||
|
|
0c4e9d4b06 | ||
|
|
ac07f5d54b | ||
|
|
d116f7b01e | ||
|
|
63ca34bc54 | ||
|
|
c894029c33 | ||
|
|
de2bdd6653 | ||
|
|
2d066ad671 | ||
|
|
24854d4ad7 | ||
|
|
99ee0af178 | ||
|
|
bf6f78df09 | ||
|
|
c05806916b | ||
|
|
e48ce0cfe3 | ||
|
|
eabca469b9 | ||
|
|
f6dc36e812 | ||
|
|
ac7cbdfd16 | ||
|
|
d1d008af0a | ||
|
|
5db56e01f5 | ||
|
|
726c49bec5 | ||
|
|
ae5db5dde7 | ||
|
|
2671807f0e | ||
|
|
70c8626016 | ||
|
|
436c472a49 | ||
|
|
ed5be6b062 | ||
|
|
a38f24728a | ||
|
|
4af026356f | ||
|
|
c6e1bb5618 | ||
|
|
e64d2e7ad4 | ||
|
|
3d393e9aa4 | ||
|
|
b8f3122ee9 | ||
|
|
8acfa6591e | ||
|
|
25b75b0e1b | ||
|
|
cb01b77f84 | ||
|
|
61582a0324 | ||
|
|
a17b5d51a4 | ||
|
|
447ea8674b | ||
|
|
c5eec850e1 | ||
|
|
1870631150 | ||
|
|
20ed1659c1 | ||
|
|
258de3efba | ||
|
|
394fd5fab9 | ||
|
|
53b7ef86ed | ||
|
|
423479771e | ||
|
|
933271826b | ||
|
|
880170ea7d | ||
|
|
e58ec37881 | ||
|
|
dece50dd10 | ||
|
|
9db9fc6009 | ||
|
|
f38c030805 | ||
|
|
d8060d3af9 | ||
|
|
34b4090b42 | ||
|
|
c6ca3d64b3 | ||
|
|
4913426631 | ||
|
|
455a1c76c3 | ||
|
|
e5a5f6f9fb | ||
|
|
7ef88d06a5 | ||
|
|
bc82d7988b | ||
|
|
545fa858e4 | ||
|
|
71c917649e | ||
|
|
3781556cf5 | ||
|
|
765d10a7c3 | ||
|
|
3583113381 | ||
|
|
46cd60e579 | ||
|
|
8198e59736 | ||
|
|
c5f0f5d177 | ||
|
|
ebc93954be | ||
|
|
cb4d2984b3 | ||
|
|
4c2018ef67 | ||
|
|
06d8295d0a | ||
|
|
ef8e9975e9 | ||
|
|
31509f4b9c | ||
|
|
497c1f393f | ||
|
|
8334a245c7 | ||
|
|
e8826a2941 | ||
|
|
ef040c1e7d | ||
|
|
dc692c8256 | ||
|
|
e9c7bfc087 | ||
|
|
22c72625c8 | ||
|
|
65a2212890 | ||
|
|
0a7c08cafc | ||
|
|
831a32160b | ||
|
|
b186419e54 | ||
|
|
1dc579c00f | ||
|
|
abdd6c319a | ||
|
|
5dd93fadfa | ||
|
|
e6fcbf59df | ||
|
|
9fd04ed301 | ||
|
|
e4b5f595ce | ||
|
|
212b02589b | ||
|
|
42c3631995 | ||
|
|
2695355dd2 | ||
|
|
db0cd861d6 | ||
|
|
57d6a962de | ||
|
|
91608d7366 | ||
|
|
7d4f1ffc45 | ||
|
|
ba539bb555 | ||
|
|
d9ecbf06c0 | ||
|
|
01df4623c7 | ||
|
|
c9cb91a22e | ||
|
|
1f9d60aecc | ||
|
|
52c5b5aade | ||
|
|
2d98173c51 | ||
|
|
5e3f13be70 | ||
|
|
90b894b88a | ||
|
|
6dc7387881 | ||
|
|
b14b017d72 | ||
|
|
d0ede18bf4 | ||
|
|
6d223aea03 | ||
|
|
f7986b0a05 | ||
|
|
629e70287c | ||
|
|
3c2a88144c | ||
|
|
3651f18566 | ||
|
|
472fea75b1 | ||
|
|
e1b5438865 | ||
|
|
fbbf523333 | ||
|
|
15fa53d744 | ||
|
|
9595f80fde | ||
|
|
61a67e45c1 | ||
|
|
143e690dab | ||
|
|
ebd507f143 | ||
|
|
f71bc0a8f7 | ||
|
|
edc0c72464 | ||
|
|
c3ce4f718b | ||
|
|
40ee482973 | ||
|
|
a5d2e6fdd2 | ||
|
|
e9a835a642 | ||
|
|
ac7b95ceb6 | ||
|
|
055b2d8ede | ||
|
|
a75cd0a0f5 | ||
|
|
2b1ddf9a4e | ||
|
|
b70442e483 | ||
|
|
798b0fab15 | ||
|
|
e8630132d7 | ||
|
|
273ae5f21a | ||
|
|
06b688fef4 | ||
|
|
59cc038ab2 | ||
|
|
ea5a6c3438 | ||
|
|
e2adfdf3cf | ||
|
|
5ee6531627 | ||
|
|
1cf8ee09e1 | ||
|
|
4a2cf9d722 | ||
|
|
6a6a32c1cf | ||
|
|
ce03611b52 | ||
|
|
bbe9f83878 | ||
|
|
40e6c6aa92 | ||
|
|
9f6ccf092a | ||
|
|
d4ba4b082f | ||
|
|
cce43829eb | ||
|
|
c1bfbac2fe | ||
|
|
fc87cde668 | ||
|
|
400c413029 | ||
|
|
0e6eb45732 | ||
|
|
7ca2028c19 | ||
|
|
08144b54a7 | ||
|
|
103137498b | ||
|
|
8e57e3709d | ||
|
|
bd6bd66946 | ||
|
|
6973dd0ec2 | ||
|
|
2e8b08cd9e | ||
|
|
edc8f43f42 | ||
|
|
133814cd73 | ||
|
|
57213ee31b | ||
|
|
b4fa2a394b | ||
|
|
0c39342d53 | ||
|
|
1c95e8e25c | ||
|
|
ff5a92772b | ||
|
|
bc2a17f70f | ||
|
|
f2e909e578 | ||
|
|
c3385a808c | ||
|
|
8d480331ff | ||
|
|
5216f0989c | ||
|
|
4238553a2e | ||
|
|
79662d0dcf | ||
|
|
ff095bc53d | ||
|
|
eefd70b2de | ||
|
|
9b3eab67a2 | ||
|
|
54def2deb7 | ||
|
|
cd12744726 | ||
|
|
616fd9570f | ||
|
|
0544011ee0 | ||
|
|
51920c7045 | ||
|
|
6f417a1775 | ||
|
|
51034a24c6 | ||
|
|
f631f219b0 | ||
|
|
aacae020b8 | ||
|
|
7c426e0eec | ||
|
|
363826502f | ||
|
|
1cfadae068 | ||
|
|
d3b3d722b4 | ||
|
|
a82428958b | ||
|
|
b185632bda | ||
|
|
e7a0c3d25b | ||
|
|
ab33de2c15 | ||
|
|
a1031cdc27 | ||
|
|
e3ab3ca506 | ||
|
|
b4cd1ccbee | ||
|
|
14573a5714 | ||
|
|
96150a9668 | ||
|
|
c10c1303ed | ||
|
|
7852e69685 | ||
|
|
094880af50 | ||
|
|
c3db518aca | ||
|
|
41ee0c5fdb | ||
|
|
ae178bc146 | ||
|
|
a2a660d483 | ||
|
|
8684cc34f7 | ||
|
|
0aba8b78ba | ||
|
|
5e735aa8d5 | ||
|
|
a2d6338400 | ||
|
|
5e4b8350ab | ||
|
|
4a65b646df | ||
|
|
24c715aae9 | ||
|
|
9ab85768bd | ||
|
|
16458d96e7 | ||
|
|
b307dd17af | ||
|
|
313552352c | ||
|
|
543ac7c649 | ||
|
|
dacff76694 | ||
|
|
c134169ea1 | ||
|
|
e252945047 | ||
|
|
f8cfcaab20 | ||
|
|
528237a239 | ||
|
|
91aefe25c4 | ||
|
|
a8c11097d9 | ||
|
|
92ba4a3ae9 | ||
|
|
7c5d382010 | ||
|
|
f8fbaefd86 | ||
|
|
181b37296a | ||
|
|
2ab61cfa12 | ||
|
|
1b0e9b14ef | ||
|
|
2aacc9266f | ||
|
|
d648d477bb | ||
|
|
6f91c1a1d3 | ||
|
|
aa1aca24b7 | ||
|
|
6a0867172f | ||
|
|
f025a4b2fb | ||
|
|
8871f36a92 | ||
|
|
f17490edad | ||
|
|
b360e782c6 | ||
|
|
8d94324dd6 | ||
|
|
2818609412 | ||
|
|
270a2e69d4 | ||
|
|
d1d9762e29 | ||
|
|
1666e8b127 | ||
|
|
08dfad208b | ||
|
|
b5921f95f3 | ||
|
|
2063bc3db3 | ||
|
|
4380016d52 | ||
|
|
5e3ceabe46 | ||
|
|
8e7936275b | ||
|
|
4b967239fa | ||
|
|
92945c384c | ||
|
|
79d93c4ecf | ||
|
|
76b6f25b0c | ||
|
|
aadce4890a | ||
|
|
0e95a98fc2 | ||
|
|
9483437e8f | ||
|
|
59542f8aef | ||
|
|
e29f7332f5 | ||
|
|
f8640feafe | ||
|
|
e94e9e2be4 | ||
|
|
4053aac365 | ||
|
|
a5fa6c7aef | ||
|
|
97263894d1 | ||
|
|
1885580958 | ||
|
|
1167b470bb | ||
|
|
7600506d6d | ||
|
|
86bad866a0 | ||
|
|
2f1a15cf7e | ||
|
|
52b0e1870f | ||
|
|
9b181c1e0d | ||
|
|
100f2dc45e | ||
|
|
b247c3d477 | ||
|
|
76ee82b258 | ||
|
|
e8fcd29669 | ||
|
|
8dd16ecea4 | ||
|
|
e9c0bcd877 | ||
|
|
75ed4b52a6 | ||
|
|
71635c00df | ||
|
|
1810af5483 | ||
|
|
b07835dfd5 | ||
|
|
4c33aa2aae | ||
|
|
3c255640cb | ||
|
|
3d08ba9ebc | ||
|
|
f64482500e | ||
|
|
215902f192 | ||
|
|
3e9c3a069d | ||
|
|
841fb48479 | ||
|
|
df8e41925f |
2
.bazelrc
Normal file
2
.bazelrc
Normal file
@@ -0,0 +1,2 @@
|
||||
build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
build --host_force_python=PY2
|
||||
25
.github/workflows/ci.yml
vendored
Normal file
25
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
branches:
|
||||
- '*'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release
|
||||
- name: Build Driver
|
||||
run: bazel build --apple_generate_dsym -c opt :release_driver
|
||||
- name: Test
|
||||
run: bazel test :unit_tests
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
Build
|
||||
santa-*
|
||||
default.profraw
|
||||
*.provisionprofile
|
||||
bazel-*
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
Santa.xcodeproj/project.xcworkspace
|
||||
|
||||
12
.travis.yml
12
.travis.yml
@@ -1,12 +0,0 @@
|
||||
---
|
||||
language: objective-c
|
||||
cache: cocoapods
|
||||
sudo: false
|
||||
osx_image: xcode7
|
||||
|
||||
before_install:
|
||||
- gem install cocoapods xcpretty
|
||||
- pod setup >/dev/null
|
||||
|
||||
script:
|
||||
- xcodebuild -workspace Santa.xcworkspace -scheme All -derivedDataPath build build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}
|
||||
221
BUILD
Normal file
221
BUILD
Normal file
@@ -0,0 +1,221 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
load("//:version.bzl", "SANTA_VERSION")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
exports_files(["LICENSE"])
|
||||
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_version = SANTA_VERSION,
|
||||
short_version_string = SANTA_VERSION,
|
||||
)
|
||||
|
||||
# Used to detect optimized builds
|
||||
config_setting(
|
||||
name = "opt_build",
|
||||
values = {"compilation_mode": "opt"},
|
||||
)
|
||||
|
||||
package_group(
|
||||
name = "santa_package_group",
|
||||
packages = ["//..."],
|
||||
)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Loading/Unloading/Reloading
|
||||
################################################################################
|
||||
run_command(
|
||||
name = "unload",
|
||||
cmd = """
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
|
||||
sudo kextunload -b com.google.santa-driver 2>/dev/null
|
||||
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
|
||||
""",
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "load",
|
||||
cmd = """
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
""",
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "reload",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"//Source/santa_driver",
|
||||
],
|
||||
cmd = """
|
||||
set -e
|
||||
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*/bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*/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
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
echo "Time to stop being naughty"
|
||||
""",
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Release rules - used to create a release tarball
|
||||
################################################################################
|
||||
genrule(
|
||||
name = "release",
|
||||
srcs = [
|
||||
"//Source/santa:Santa",
|
||||
"Conf/install.sh",
|
||||
"Conf/uninstall.sh",
|
||||
"Conf/com.google.santa.bundleservice.plist",
|
||||
"Conf/com.google.santad.plist",
|
||||
"Conf/com.google.santa.plist",
|
||||
"Conf/com.google.santa.asl.conf",
|
||||
"Conf/com.google.santa.newsyslog.conf",
|
||||
"Conf/Package/Makefile",
|
||||
"Conf/Package/postinstall",
|
||||
"Conf/Package/preinstall",
|
||||
],
|
||||
outs = ["santa-" + 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.zip
|
||||
for SRC in $(SRCS); do
|
||||
if [ "$$(basename $${SRC})" == "Santa.zip" ]; then
|
||||
mkdir -p $(@D)/binaries
|
||||
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy config files
|
||||
for SRC in $(SRCS); do
|
||||
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
|
||||
mkdir -p $(@D)/conf
|
||||
cp $${SRC} $(@D)/conf/
|
||||
fi
|
||||
done
|
||||
|
||||
# Gather together the dSYMs. Throw an error if no dSYMs were found
|
||||
for SRC in $(SRCS); do
|
||||
case $${SRC} in
|
||||
*santad.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santad.dSYM
|
||||
;;
|
||||
*santactl.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santactl.dSYM
|
||||
;;
|
||||
*santabundleservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
|
||||
;;
|
||||
*Santa.app.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
|
||||
;;
|
||||
*com.google.santa.daemon.systemextension.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/com.google.santa.daemon.systemextension.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,conf,dsym} -exec touch {} \\;
|
||||
|
||||
# Create final output tar
|
||||
tar -C $(@D) -czpf $(@) binaries dsym conf
|
||||
""",
|
||||
}),
|
||||
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/santactl:SNTCommandFileInfoTest",
|
||||
"//Source/santactl:SNTCommandSyncTest",
|
||||
"//Source/santad:SNTEventTableTest",
|
||||
"//Source/santad:SNTExecutionControllerTest",
|
||||
"//Source/santad:SNTRuleTableTest",
|
||||
],
|
||||
)
|
||||
@@ -13,18 +13,15 @@ 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.
|
||||
with your idea so that we can help out and possibly guide you. Co-ordinating
|
||||
large changes ahead of time can avoid frustration later on.
|
||||
|
||||
### Code reviews
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. It's also a good idea to run the
|
||||
tests beforehand, which you can do with the following commands:
|
||||
All submissions - including submissions by project members - require review. We
|
||||
use GitHub pull requests for this purpose. GitHub will automatically run the
|
||||
tests when you mail your pull request and a proper review won't be started until
|
||||
the tests are complete and passing.
|
||||
|
||||
```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,
|
||||
|
||||
@@ -32,6 +32,7 @@ PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/rele
|
||||
# | |-- com.google.santad.plist
|
||||
# | |-- com.google.santagui.plist
|
||||
# | +-- com.google.santa.asl.conf
|
||||
# | +-- com.google.santa.newsyslog.conf
|
||||
# +--dsym
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
@@ -44,6 +45,7 @@ PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
|
||||
pack-Library-LaunchDaemons-com.google.santad.plist \
|
||||
pack-Library-LaunchAgents-com.google.santagui.plist \
|
||||
pack-etc-asl-com.google.santa.asl.conf \
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf \
|
||||
pack-script-preinstall \
|
||||
pack-script-postinstall
|
||||
|
||||
@@ -52,6 +54,7 @@ Santa.app: download
|
||||
com.google.santad.plist: download
|
||||
com.google.santagui.plist: download
|
||||
com.google.santa.asl.conf: download
|
||||
com.google.santa.newsyslog.conf: download
|
||||
|
||||
download:
|
||||
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
@@ -65,6 +68,12 @@ pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/asl
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
|
||||
|
||||
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf: com.google.santa.newsyslog.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/newsyslog.d
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.newsyslog.conf ${WORK_D}/private/etc/newsyslog.d
|
||||
|
||||
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
|
||||
@sudo mkdir -p ${WORK_D}/Library/Extensions
|
||||
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
@@ -79,6 +88,8 @@ myclean:
|
||||
@rm -rf santa-driver.kext
|
||||
@rm -f config.plist
|
||||
@rm -f com.google.santa.asl.conf
|
||||
@rm -f com.google.santa.newsyslog.conf
|
||||
@rm -f com.google.santad.plist
|
||||
@rm -f com.google.santagui.plist
|
||||
@rm -f install.sh
|
||||
@rm -f uninstall.sh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Load the kernel extension, santad, sync client
|
||||
# Load com.google.santa.daemon and com.google.santa.bundleservice
|
||||
# If a user is logged in, also load the GUI agent.
|
||||
# If the target volume is not /, do nothing
|
||||
|
||||
@@ -9,19 +9,23 @@
|
||||
# Restart syslogd to pick up ASL configuration change
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
/sbin/kextload /Library/Extensions/santa-driver.kext
|
||||
# Create hopefully useful symlink for santactl
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
sleep 1
|
||||
# Remove the kext before com.google.santa.daemon loads if the SystemExtension is already present.
|
||||
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && rm -rf /Library/Extensions/santa-driver.kext
|
||||
|
||||
# Load com.google.santa.daemon, its main has logic to handle loading the kext
|
||||
# or relaunching itself as a SystemExtension.
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
|
||||
|
||||
sleep 1
|
||||
# Load com.google.santa.bundleservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
# Create hopefully useful symlink for santactl
|
||||
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "$user" ]] && exit 0
|
||||
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
exit 0
|
||||
|
||||
@@ -6,21 +6,26 @@
|
||||
|
||||
[[ $3 != "/" ]] && exit 0
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
/bin/launchctl remove com.google.santad || true
|
||||
/bin/launchctl remove com.google.santa.bundleservice || true
|
||||
|
||||
sleep 1
|
||||
/bin/sleep 1
|
||||
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1 || true
|
||||
|
||||
# Remove cruft from old Santa versions
|
||||
/bin/rm /usr/libexec/santad
|
||||
/bin/rm /usr/sbin/santactl
|
||||
/bin/rm -f /usr/libexec/santad
|
||||
/bin/rm -f /usr/sbin/santactl
|
||||
/bin/launchctl remove com.google.santasync
|
||||
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santasync.plist
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
|
||||
sleep 1
|
||||
/bin/sleep 1
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santagui
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santa
|
||||
exit 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
|
||||
> /var/log/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
|
||||
> /var/db/santa/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
|
||||
? [= Sender kernel] [S= Message santa-driver:] claim
|
||||
? [= Sender kernel] [S= Message santa-driver:] file /var/log/santa.log
|
||||
? [= Sender kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
|
||||
? [= Facility com.google.santa] claim
|
||||
? [= Facility com.google.santa] file /var/log/santa.log
|
||||
? [= Facility com.google.santa] file /var/db/santa/santa.log
|
||||
|
||||
26
Conf/com.google.santa.bundleservice.plist
Normal file
26
Conf/com.google.santa.bundleservice.plist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.bundleservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santabundleservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.bundleservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<false/>
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
2
Conf/com.google.santa.newsyslog.conf
Normal file
2
Conf/com.google.santa.newsyslog.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
|
||||
/var/db/santa/santa.log root:wheel 644 10 25000 * NZ
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santagui</string>
|
||||
<string>com.google.santa</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>
|
||||
22
Conf/com.google.santa.syncservice.plist
Normal file
22
Conf/com.google.santa.syncservice.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.syncservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santasyncservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.syncservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,26 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>SantaXPCControl</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true />
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>1</integer>
|
||||
<key>Label</key>
|
||||
<string>com.google.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.daemon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Minimal Configuration -->
|
||||
<key>ClientMode</key>
|
||||
<integer>1</integer>
|
||||
|
||||
<!-- For documentation of other keys, see the following URL:
|
||||
https://github.com/google/santa/wiki/Configuration-Keys -->
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -5,54 +5,74 @@ if [[ $EUID -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -d "binaries" ]]; then
|
||||
SOURCE="."
|
||||
elif [[ -d "../binaries" ]]; then
|
||||
SOURCE=".."
|
||||
else
|
||||
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
|
||||
exit 1
|
||||
if [[ -z "${BINARIES}" || -z "${CONF}" ]]; then
|
||||
if [[ -d "binaries" ]]; then
|
||||
BINARIES="${PWD}/binaries"
|
||||
CONF="${PWD}/conf"
|
||||
elif [[ -d "../binaries" ]]; then
|
||||
BINARIES="${PWD}/../binaries"
|
||||
CONF="${PWD}/../conf"
|
||||
else
|
||||
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
# Unload santad and scheduled sync job.
|
||||
/bin/launchctl remove com.google.santad >/dev/null 2>&1
|
||||
|
||||
# Unload bundle service
|
||||
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
# Unload GUI agent if someone is logged in.
|
||||
[[ -n "${GUI_USER}" ]] && \
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santagui
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santa
|
||||
|
||||
# Cleanup cruft from old versions
|
||||
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
|
||||
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist >/dev/null 2>&1
|
||||
/bin/rm /usr/libexec/santad >/dev/null 2>&1
|
||||
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
|
||||
/bin/rm -rf /Applications/Santa.app 2>&1
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
|
||||
|
||||
# Copy new files.
|
||||
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
|
||||
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
|
||||
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
/bin/mkdir -p /var/db/santa
|
||||
|
||||
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
|
||||
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
|
||||
/bin/cp -r ${BINARIES}/Santa.app /Applications
|
||||
|
||||
# Only copy the kext if the SystemExtension is not present.
|
||||
# This prevents Santa from dueling itself if the "EnableSystemExtension" config is set to false.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 || /bin/cp -r ${BINARIES}/santa-driver.kext /Library/Extensions && /usr/sbin/kextcache -update-volume / -bundle-id com.google.santa-driver
|
||||
|
||||
/bin/mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
|
||||
|
||||
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
|
||||
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
|
||||
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
# Reload syslogd to pick up ASL configuration change.
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
# Load kext.
|
||||
/sbin/kextload /Library/Extensions/santa-driver.kext
|
||||
|
||||
# Load santad and scheduled sync jobs.
|
||||
# Load com.google.santa.daemon
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
|
||||
# Load com.google.santa.bundleservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl load -w /Library/LaunchAgents/com.google.santa.plist
|
||||
exit 0
|
||||
|
||||
34
Conf/uninstall.sh
Executable file
34
Conf/uninstall.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Uninstalls Santa from the boot volume, clearing up everything but logs/configs.
|
||||
# Unloads the kernel extension, services, and deletes component files.
|
||||
# If a user is logged in, also unloads the GUI agent.
|
||||
|
||||
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
|
||||
|
||||
# For macOS 10.15+ this will block up to 60 seconds
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && /Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santa
|
||||
# and to clean out the log config, although it won't write after wiping the binary
|
||||
/usr/bin/killall -HUP syslogd
|
||||
# delete artifacts on-disk
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
|
||||
/bin/rm -f /usr/local/bin/santactl # just a symlink
|
||||
|
||||
#uncomment to remove the config file and all databases, log files
|
||||
#/bin/rm -rf /var/db/santa
|
||||
#/bin/rm -f /var/log/santa*
|
||||
exit 0
|
||||
4
Fuzzing/libFuzzer/.gitignore
vendored
Normal file
4
Fuzzing/libFuzzer/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
bin
|
||||
llvm-*.src
|
||||
llvm-*.src.tar.xz
|
||||
|
||||
109
Fuzzing/libFuzzer/build.sh
Executable file
109
Fuzzing/libFuzzer/build.sh
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LLVM_VERSION='5.0.1'
|
||||
LLVM_COMPILERRT_TARBALL_NAME="llvm-${LLVM_VERSION}.src.tar.xz"
|
||||
LLVM_COMPILERRT_SRC_FOLDER_NAME=`echo "${LLVM_COMPILERRT_TARBALL_NAME}" | cut -d '.' -f 1-4`
|
||||
LLVM_COMPILERRT_TARBALL_URL="http://releases.llvm.org/${LLVM_VERSION}/${LLVM_COMPILERRT_TARBALL_NAME}"
|
||||
|
||||
LIBFUZZER_FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
LOG_FILE=`mktemp`
|
||||
|
||||
main() {
|
||||
echo "libFuzzer build script"
|
||||
|
||||
echo " > Checking dependencies..."
|
||||
checkDependencies || return 1
|
||||
|
||||
echo " > Entering libFuzzer folder..."
|
||||
cd "${LIBFUZZER_FOLDER}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to enter the libFuzzer folder: ${LIBFUZZER_FOLDER}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -f "${LLVM_COMPILERRT_TARBALL_NAME}" ] ; then
|
||||
echo " > Downloading the LLVM tarball..."
|
||||
curl "${LLVM_COMPILERRT_TARBALL_URL}" -o "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to download the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo " > An existing LLVM tarball was found"
|
||||
fi
|
||||
|
||||
if [ -d "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" ] ; then
|
||||
echo " > Deleting existing LLVM folder..."
|
||||
rm -rf "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing source folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " > Extracting the LLVM tarball..."
|
||||
tar xf "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
rm "${LLVM_COMPILERRT_TARBALL_NAME}" "${LLVM_COMPILERRT_SRC_FOLDER_NAME}"
|
||||
dumpLogFile "Failed to extract the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -d "bin" ] ; then
|
||||
echo " > Deleting existing bin folder..."
|
||||
rm -rf "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing bin folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to create the bin folder"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " > Building libFuzzer..."
|
||||
( cd "bin" && "../${LLVM_COMPILERRT_SRC_FOLDER_NAME}/lib/Fuzzer/build.sh" ) > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to build the library"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf "\nFinished building libFuzzer\n"
|
||||
rm "${LOG_FILE}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkDependencies() {
|
||||
executable_list=( "clang++" "curl" "tar" )
|
||||
|
||||
for executable in "${executable_list[@]}" ; do
|
||||
which "${executable}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "The following program was not found: ${executable}"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
dumpLogFile() {
|
||||
if [ $# -eq 1 ] ; then
|
||||
local message="$1"
|
||||
else
|
||||
local message="An error has occurred"
|
||||
fi
|
||||
|
||||
printf "${message}\n"
|
||||
printf "Log file follows\n===\n"
|
||||
cat "${LOG_FILE}"
|
||||
printf "\n===\n"
|
||||
rm "${LOG_FILE}"
|
||||
}
|
||||
|
||||
main $@
|
||||
exit $?
|
||||
3
Fuzzing/santacache/.gitignore
vendored
Normal file
3
Fuzzing/santacache/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
santacache.dSYM
|
||||
santacache
|
||||
|
||||
BIN
Fuzzing/santacache/santacache_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santacache/santacache_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
41
Fuzzing/santacache/src/main.cpp
Normal file
41
Fuzzing/santacache/src/main.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <SantaCache.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
|
||||
|
||||
std::uint64_t fields[2] = {};
|
||||
|
||||
if (size > 16) {
|
||||
std::cout << "Invalid size! Start with -max_len=16\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::memcpy(fields, data, size);
|
||||
|
||||
decision_cache.set(fields[0], fields[1]);
|
||||
auto returned_value = decision_cache.get(fields[0]);
|
||||
|
||||
if (returned_value != fields[1]) {
|
||||
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
16
Fuzzing/santactl/santactl_fuzzer_seed_corpus/example01
Normal file
16
Fuzzing/santactl/santactl_fuzzer_seed_corpus/example01
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"rule_type": "BINARY",
|
||||
"policy": "BLACKLIST",
|
||||
"sha256": "2dc104631939b4bdf5d6bccab76e166e37fe5e1605340cf68dab919df58b8eda",
|
||||
"custom_msg": "blacklist firefox"
|
||||
},
|
||||
{
|
||||
"rule_type": "CERTIFICATE",
|
||||
"policy": "BLACKLIST",
|
||||
"sha256": "e7726cf87cba9e25139465df5bd1557c8a8feed5c7dd338342d8da0959b63c8d",
|
||||
"custom_msg": "blacklist dash app certificate"
|
||||
}
|
||||
]
|
||||
}
|
||||
62
Fuzzing/santactl/src/main.mm
Normal file
62
Fuzzing/santactl/src/main.mm
Normal file
@@ -0,0 +1,62 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <SNTCommandSyncRuleDownload.h>
|
||||
#include <SNTCommandSyncState.h>
|
||||
#include <SNTCommandSyncConstants.h>
|
||||
#include <SNTRule.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
|
||||
if (!buffer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error];
|
||||
if (!response) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (![response isKindOfClass:[NSDictionary class]]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (![response objectForKey:kRules]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTCommandSyncState *state = [[SNTCommandSyncState alloc] init];
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTCommandSyncRuleDownload *obj = [[SNTCommandSyncRuleDownload alloc] initWithState:state];
|
||||
if (!obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (NSDictionary *ruleDict in response[kRules]) {
|
||||
SNTRule *rule = [obj ruleFromDictionary:ruleDict];
|
||||
if (rule) {
|
||||
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
Fuzzing/santad/santad_checkCacheForVnodeID_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santad/santad_checkCacheForVnodeID_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
К'.p▒└G╗М┐║ЙSЮ╝и▌РУерЭxt1iАЫШ9ы*H╩4R"═©$-├Уww╙+Р╝╘[┼иу╧oС┬ОwRpЗя≤х°е
|
||||
BIN
Fuzzing/santad/santad_databaseRuleAddRules_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santad/santad_databaseRuleAddRules_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
55
Fuzzing/santad/src/checkCacheForVnodeID.mm
Normal file
55
Fuzzing/santad/src/checkCacheForVnodeID.mm
Normal file
@@ -0,0 +1,55 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > 16) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= 16)" << std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
santa_vnode_id_t vnodeID = {};
|
||||
std::memcpy(&vnodeID, data, size);
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;;
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
51
Fuzzing/santad/src/databaseRemoveEventsWithIDs.mm
Normal file
51
Fuzzing/santad/src/databaseRemoveEventsWithIDs.mm
Normal file
@@ -0,0 +1,51 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
auto *eventId = reinterpret_cast<const std::uint64_t *>(data);
|
||||
std::size_t eventIdCount = size / sizeof(std::uint64_t);
|
||||
if (eventIdCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:eventIdCount];
|
||||
for (std::size_t i = 0; i < eventIdCount; i++) {
|
||||
auto id = [NSNumber numberWithInteger:eventId[i]];
|
||||
[eventIds addObject:id];
|
||||
}
|
||||
|
||||
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
|
||||
return 0;
|
||||
}
|
||||
73
Fuzzing/santad/src/databaseRuleAddRules.mm
Normal file
73
Fuzzing/santad/src/databaseRuleAddRules.mm
Normal file
@@ -0,0 +1,73 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct InputData {
|
||||
std::uint32_t cleanSlate;
|
||||
std::uint32_t state;
|
||||
std::uint32_t type;
|
||||
char hash[33];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > sizeof(InputData)) {
|
||||
std::cerr << "Invalid buffer size of " << size
|
||||
<< " (should be <= " << sizeof(InputData)
|
||||
<< ")" << std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
InputData input_data = {};
|
||||
std::memcpy(&input_data, data, size);
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.state = (SNTRuleState) input_data.state;
|
||||
newRule.type = (SNTRuleType) input_data.type;
|
||||
newRule.shasum = @(input_data.hash);
|
||||
newRule.customMsg = @"";
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
58
Podfile
58
Podfile
@@ -1,48 +1,22 @@
|
||||
platform :osx, "10.9"
|
||||
|
||||
inhibit_all_warnings!
|
||||
|
||||
target :Santa do
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
def common_pods
|
||||
pod 'MOLXPCConnection'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'FMDB'
|
||||
pod 'MOLCertificate'
|
||||
pod 'OCMock'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLFCMClient'
|
||||
end
|
||||
|
||||
target :santad do
|
||||
pod 'FMDB'
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
end
|
||||
project './Santa.xcodeproj'
|
||||
|
||||
target :santactl do
|
||||
pod 'FMDB'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
end
|
||||
|
||||
target :LogicTests do
|
||||
pod 'FMDB'
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'OCMock'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if config.name != 'Release' then
|
||||
break
|
||||
end
|
||||
|
||||
# This is necessary to get FMDB to not NSLog stuff.
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
|
||||
|
||||
# Enable more compiler optimizations.
|
||||
config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 'fast'
|
||||
config.build_settings['LLVM_LTO'] = 'YES'
|
||||
end
|
||||
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
|
||||
|
||||
|
||||
50
Podfile.lock
50
Podfile.lock
@@ -1,28 +1,46 @@
|
||||
PODS:
|
||||
- FMDB (2.6.2):
|
||||
- FMDB/standard (= 2.6.2)
|
||||
- FMDB/standard (2.6.2)
|
||||
- MOLAuthenticatingURLSession (1.6):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- MOLCertificate (1.5)
|
||||
- MOLCodesignChecker (1.5):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- OCMock (3.3)
|
||||
- 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: 854a0341b4726e53276f2a8996f06f1b80f9259a
|
||||
MOLAuthenticatingURLSession: f956240458fb24b61e5607d735948dc9babfb4e3
|
||||
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
|
||||
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
|
||||
OCMock: d68685bde31f69cb61d518dcb39269080c78b5ed
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
|
||||
MOLCertificate: e9e88a396c57032cab847f51a46e20c730cd752a
|
||||
MOLCodesignChecker: b0d5db9d2f9bd94e0fd093891a5d40e5ad77cbc0
|
||||
MOLFCMClient: 2bfbacd45cc11e1ca3c077e97b80401c4e4a54f1
|
||||
MOLXPCConnection: c27af5cb1c43b18319698b0e568a8ddc2fc1e306
|
||||
OCMock: 4ab4577fc941af31f4a0398f6e7e230cf21fc72a
|
||||
|
||||
PODFILE CHECKSUM: bc456d69693ca262c781dbbde40529a9474b84b5
|
||||
PODFILE CHECKSUM: d03767a9915896232523962c98d9ff7294aec2b7
|
||||
|
||||
COCOAPODS: 1.0.1
|
||||
COCOAPODS: 1.10.0
|
||||
|
||||
249
README.md
249
README.md
@@ -1,164 +1,185 @@
|
||||
Santa [](https://travis-ci.org/google/santa)
|
||||
=====
|
||||
# Santa 
|
||||
|
||||
<p align="center">
|
||||
<a href="#santa--">
|
||||
<img src="./Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</a>
|
||||
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</p>
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for macOS. It consists of
|
||||
a kernel extension that monitors for executions, a userland daemon that makes
|
||||
execution decisions based on the contents of a SQLite database, a GUI agent that
|
||||
notifies the user in case of a block decision and a command-line utility for
|
||||
managing the system and synchronizing the database with a server.
|
||||
Santa is a binary authorization system for macOS. It consists of a kernel
|
||||
extension (or a system extension on macOS 10.15+) that monitors for executions,
|
||||
a userland daemon that makes execution decisions based on the contents of a
|
||||
SQLite database, a GUI agent that notifies the user in case of a block decision
|
||||
and a command-line utility for managing the system and synchronizing the
|
||||
database with a server.
|
||||
|
||||
Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
|
||||
and finishing up a security audit.
|
||||
|
||||
Santa is named because it keeps track of binaries that are naughty and nice.
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
Santa is a project of Google's Macintosh Operations Team.
|
||||
|
||||
Features
|
||||
========
|
||||
# Docs
|
||||
|
||||
* Multiple modes: MONITOR and LOCKDOWN. In MONITOR mode all binaries except
|
||||
those marked as blacklisted will be allowed to run, whilst being logged and
|
||||
recorded in the database. In LOCKDOWN mode, only whitelisted binaries are
|
||||
allowed to run.
|
||||
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.
|
||||
|
||||
* Codesign listing: Binaries can be whitelisted/blacklisted by their signing
|
||||
certificate, so you can trust/block all binaries by a given publisher. The
|
||||
binary will only be whitelisted by certificate if its signature validates
|
||||
correctly. However, a decision for a binary will override a decision for a
|
||||
certificate; i.e. you can whitelist a certificate while blacklisting a binary
|
||||
signed by that certificate or vice-versa.
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
instructions for developing Santa itself.
|
||||
|
||||
* In-kernel caching: whitelisted binaries are cached in the kernel so the
|
||||
processing required to make a request is only done if the binary
|
||||
isn't already cached.
|
||||
# Get Help
|
||||
|
||||
* Userland components validate each other: each of the userland components (the
|
||||
daemon, the GUI agent and the command-line utility) communicate with each other
|
||||
using XPC and check that their signing certificates are identical before any
|
||||
communication is accepted.
|
||||
If you have questions or otherwise need help getting started,
|
||||
the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
|
||||
great place.
|
||||
|
||||
* Event logging: all executions processed by the userland agent are logged and
|
||||
all unknown or denied binaries are also stored in the database for upload to a
|
||||
server.
|
||||
If you believe you have a bug, feel free to report [an
|
||||
issue](https://github.com/google/santa/isues) and we'll respond as soon as we
|
||||
can.
|
||||
|
||||
* Kext uses only KPIs: the kernel extension only uses provided kernel
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
If you believe you've found a vulnerability, please read the
|
||||
[security policy](https://github.com/google/santa/security/policy) for
|
||||
disclosure reporting.
|
||||
|
||||
# Admin-Related Features
|
||||
|
||||
* Multiple modes: In the default MONITOR mode, all binaries except those marked
|
||||
as blocked will be allowed to run, whilst being logged and recorded in
|
||||
the events database. In LOCKDOWN mode, only listed binaries are allowed to
|
||||
run.
|
||||
|
||||
* Event logging: When the kext is loaded, all binary launches are logged. When
|
||||
in either mode, all unknown or denied binaries are stored in the database to
|
||||
enable later aggregation.
|
||||
|
||||
* Certificate-based rules, with override levels: Instead of relying on a
|
||||
binary's hash (or 'fingerprint'), executables can be allowed/blocked by their
|
||||
signing certificate. You can therefore allow/block all binaries by a
|
||||
given publisher that were signed with that cert across version updates. A
|
||||
binary can only be allowed by its certificate if its signature validates
|
||||
correctly but a rule for a binary's fingerprint will override a decision for
|
||||
a certificate; i.e. you can allowlist a certificate while blocking a binary
|
||||
signed with that certificate, or vice-versa.
|
||||
|
||||
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature
|
||||
to that found in Managed Client (the precursor to configuration profiles,
|
||||
which used the same implementation mechanism), Application Launch
|
||||
Restrictions via the mcxalr binary. This implementation carries the added
|
||||
benefit of being configurable via regex, and not relying on LaunchServices.
|
||||
As detailed in the wiki, when evaluating rules this holds the lowest
|
||||
precedence.
|
||||
|
||||
* Failsafe cert rules: You cannot put in a deny rule that would block the
|
||||
certificate used to sign launchd, a.k.a. pid 1, and therefore all components
|
||||
used in macOS. The binaries in every OS update (and in some cases entire new
|
||||
versions) are therefore automatically allowed. This does not affect binaries
|
||||
from Apple's App Store, which use various certs that change regularly for
|
||||
common apps. Likewise, you cannot block Santa itself, and Santa uses a
|
||||
distinct separate cert than other Google apps.
|
||||
|
||||
# Intentions and Expectations
|
||||
|
||||
Intentions and Expectations
|
||||
===========================
|
||||
No single system or process will stop *all* attacks, or provide 100% security.
|
||||
Santa is written with the intention of helping protect users from themselves.
|
||||
People often download malware and trust it, giving the malware credentials, or
|
||||
allowing unknown software to exfiltrate more data about your system. As a
|
||||
centrally managed component, Santa can help stop the spread of malware among a
|
||||
larger fleet of machines. Additionally, Santa can aid in analyzing what is
|
||||
running in your fleet.
|
||||
large fleet of machines. Independently, Santa can aid in analyzing what is
|
||||
running on your computer.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to protect
|
||||
hosts in whatever other ways you see fit.
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to
|
||||
protect hosts in whatever other ways you see fit.
|
||||
|
||||
Get Help
|
||||
========
|
||||
# Security and Performance-Related Features
|
||||
|
||||
If you have questions or need help getting started, the
|
||||
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is the
|
||||
best place to start.
|
||||
* In-kernel caching: allowed binaries are cached in the kernel so the
|
||||
processing required to make a request is only done if the binary isn't
|
||||
already cached.
|
||||
|
||||
Known Issues
|
||||
============
|
||||
Santa is not yet a 1.0 and we have some known issues to be aware of:
|
||||
* Userland components validate each other: each of the userland components (the
|
||||
daemon, the GUI agent and the command-line utility) communicate with each
|
||||
other using XPC and check that their signing certificates are identical
|
||||
before any communication is accepted.
|
||||
|
||||
* Kext uses only KPIs: the kernel extension only uses provided kernel
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
|
||||
# Known Issues
|
||||
|
||||
* Santa only blocks execution (execve and variants), it doesn't protect against
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been replaced or
|
||||
libraries loaded using `DYLD_INSERT_LIBRARIES`. We are working on also protecting
|
||||
against these avenues of attack.
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been
|
||||
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version
|
||||
0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were
|
||||
exploited in some versions of macOS. We are working on also protecting
|
||||
against similar avenues of attack.
|
||||
|
||||
* Kext communication security: the kext will only accept a connection from a
|
||||
single client at a time and said client must be running as root. We haven't yet
|
||||
found a good way to ensure the kext only accepts connections from a valid client.
|
||||
single client at a time and said client must be running as root. We haven't
|
||||
yet found a good way to ensure the kext only accepts connections from a valid
|
||||
client.
|
||||
|
||||
* Database protection: the SQLite database is installed with permissions so that
|
||||
only the root user can read/write it. We're considering approaches to secure
|
||||
this further.
|
||||
|
||||
* Sync client: the command-line client includes a command to synchronize with a
|
||||
management server, including the uploading of events that have occurred on the
|
||||
machine and to download new rules. We're still very heavily working on this
|
||||
server (which is AppEngine-based and will be open-sourced in the future), so the
|
||||
sync client code is unfinished. It does show the 'API' that we're expecting to
|
||||
use so if you'd like to write your own management server, feel free to look at
|
||||
how the client currently works (and suggest changes!)
|
||||
* Database protection: the SQLite database is installed with permissions so
|
||||
that only the root user can read/write it. We're considering approaches to
|
||||
secure this further.
|
||||
|
||||
* Scripts: Santa is currently written to ignore any execution that isn't a
|
||||
binary. This is because after weighing the administration cost vs the benefit,
|
||||
we found it wasn't worthwhile. Additionally, a number of applications make use
|
||||
of temporary generated scripts, which we can't possibly whitelist and not doing
|
||||
so would cause problems. We're happy to revisit this (or at least make it an
|
||||
option) if it would be useful to others.
|
||||
binary. This is because after weighing the administration cost vs the
|
||||
benefit, we found it wasn't worthwhile. Additionally, a number of
|
||||
applications make use of temporary generated scripts, which we can't possibly
|
||||
allowlist and not doing so would cause problems. We're happy to revisit this
|
||||
(or at least make it an option) if it would be useful to others.
|
||||
|
||||
* Documentation: There currently isn't any.
|
||||
# Sync Servers
|
||||
|
||||
* Tests: There aren't enough of them.
|
||||
* The `santactl` command-line client includes a flag to synchronize with a
|
||||
management server, which uploads events that have occurred on the machine and
|
||||
downloads new rules. There are several open-source servers you can sync with:
|
||||
|
||||
Screenshots
|
||||
===========
|
||||
* [Upvote](https://github.com/google/upvote) - An AppEngine-based server
|
||||
that implements social voting to make managing a large fleet easier.
|
||||
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
|
||||
serves hardcoded rules from simple configuration files.
|
||||
* [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.
|
||||
|
||||
A tool like Santa doesn't really lend itself to screenshots, so here's a video instead.
|
||||
* Alternatively, `santactl` can configure rules locally (without a sync
|
||||
server).
|
||||
|
||||
<p align="center">
|
||||
<img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif" alt="Santa Block Video" />
|
||||
</p>
|
||||
# Screenshots
|
||||
|
||||
Building
|
||||
========
|
||||
```sh
|
||||
git clone https://github.com/google/santa
|
||||
cd santa
|
||||
A tool like Santa doesn't really lend itself to screenshots, so here's a video
|
||||
instead.
|
||||
|
||||
# Build a debug build. This will install any necessary CocoaPods, create the
|
||||
# workspace and build, outputting the full log only if an error occurred.
|
||||
# If CocoaPods is not installed, you'll be prompted to install it.
|
||||
#
|
||||
# For other build/install/run options, run rake without any arguments
|
||||
rake build:debug
|
||||
```
|
||||
|
||||
Note: the Xcode project is setup to use any installed "Mac Developer" certificate
|
||||
and for security-reasons parts of Santa will not operate properly if not signed.
|
||||
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
|
||||
|
||||
Kext Signing
|
||||
============
|
||||
# Kext Signing
|
||||
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
|
||||
Developer ID certificate with a kernel extension flag. Without it, the only way
|
||||
to load an extension is to enable kext-dev-mode or disable SIP, depending on the
|
||||
OS version.
|
||||
to load an extension is to enable kext-dev-mode or disable SIP, depending on
|
||||
the OS version.
|
||||
|
||||
There are two possible solutions for this, for distribution purposes:
|
||||
|
||||
1) Use a [pre-built, pre-signed version](https://github.com/google/santa/releases)
|
||||
of the kext that we supply. Each time changes are made to the kext code we will
|
||||
update the pre-built version that you can make use of. This doesn't prevent you
|
||||
from making changes to the non-kext parts of Santa and distributing those.
|
||||
If you make changes to the kext and make a pull request, we can merge them in
|
||||
and distribute a new version of the pre-signed kext.
|
||||
1) Use a [pre-built, pre-signed
|
||||
version](https://github.com/google/santa/releases) of the kext that we supply.
|
||||
Each time changes are made to the kext code we will update the pre-built
|
||||
version that you can make use of. This doesn't prevent you from making changes
|
||||
to the non-kext parts of Santa and distributing those. If you make changes to
|
||||
the kext and make a pull request, we can merge them in and distribute a new
|
||||
version of the pre-signed kext.
|
||||
|
||||
2) Apply for your own [kext signing certificate](https://developer.apple.com/contact/kext/).
|
||||
Apple will only grant this for broad distribution within an organization, they
|
||||
won't issue them just for testing purposes.
|
||||
2) Apply for your own [kext signing
|
||||
certificate](https://developer.apple.com/contact/kext/). Apple will only grant
|
||||
this for broad distribution within an organization, they won't issue them just
|
||||
for testing purposes.
|
||||
|
||||
Contributing
|
||||
============
|
||||
Patches to this project are very much welcome. Please see the [CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
|
||||
# Contributing
|
||||
Patches to this project are very much welcome. Please see the
|
||||
[CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
|
||||
file.
|
||||
|
||||
Disclaimer
|
||||
==========
|
||||
# Disclaimer
|
||||
This is **not** an official Google product.
|
||||
|
||||
212
Rakefile
212
Rakefile
@@ -1,212 +0,0 @@
|
||||
WORKSPACE = 'Santa.xcworkspace'
|
||||
DEFAULT_SCHEME = 'All'
|
||||
OUTPUT_PATH = 'Build'
|
||||
BINARIES = ['Santa.app', 'santa-driver.kext']
|
||||
DSYMS = ['Santa.app.dSYM', 'santa-driver.kext.dSYM', 'santad.dSYM', 'santactl.dSYM']
|
||||
XCPRETTY_DEFAULTS = '-sc'
|
||||
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
$DISABLE_XCPRETTY = false
|
||||
|
||||
task :default do
|
||||
system("rake -sT")
|
||||
end
|
||||
|
||||
def xcodebuild(opts)
|
||||
command = "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts}"
|
||||
if not $DISABLE_XCPRETTY
|
||||
command << " | xcpretty #{XCPRETTY_DEFAULTS} && exit ${PIPESTATUS[0]}"
|
||||
end
|
||||
|
||||
if system command
|
||||
puts "\e[32mPass\e[0m"
|
||||
else
|
||||
raise "\e[31mFail\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
def xcodebuilddir
|
||||
if not $xcode_build_dir
|
||||
output = `xcodebuild #{XCODEBUILD_DEFAULTS} -scheme All -showBuildSettings`
|
||||
if match = output.match(/BUILD_DIR = (.*)/)
|
||||
$xcode_build_dir = match.captures.first
|
||||
puts "Found Xcode build dir #{$xcode_build_dir}"
|
||||
end
|
||||
end
|
||||
$xcode_build_dir
|
||||
end
|
||||
|
||||
task :init do
|
||||
unless File.exists?(WORKSPACE) and File.exists?('Pods')
|
||||
puts "Pods missing, running 'pod install'"
|
||||
system "pod install" or raise "CocoaPods is not installed. Install with 'sudo gem install cocoapods'"
|
||||
end
|
||||
unless system 'xcpretty -v >/dev/null 2>&1'
|
||||
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
|
||||
$DISABLE_XCPRETTY = true
|
||||
end
|
||||
end
|
||||
|
||||
task :remove_existing do
|
||||
system 'sudo rm -rf /Library/Extensions/santa-driver.kext'
|
||||
system 'sudo rm -rf /Applications/Santa.app'
|
||||
end
|
||||
|
||||
desc "Clean"
|
||||
task :clean => :init do
|
||||
puts "Cleaning"
|
||||
FileUtils.rm_rf(OUTPUT_PATH)
|
||||
end
|
||||
|
||||
# Build
|
||||
namespace :build do
|
||||
desc "Build: Debug"
|
||||
task :debug do
|
||||
Rake::Task['build:build'].invoke("Debug")
|
||||
end
|
||||
|
||||
desc "Build: Release"
|
||||
task :release do
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
end
|
||||
|
||||
task :build, [:configuration] => :init do |t, args|
|
||||
config = args[:configuration]
|
||||
puts "Building with configuration: #{config}"
|
||||
xcodebuild("-scheme All -configuration #{config} build")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Install
|
||||
namespace :install do
|
||||
desc "Install: Debug"
|
||||
task :debug do
|
||||
Rake::Task['install:install'].invoke("Debug")
|
||||
end
|
||||
|
||||
desc "Install: Release"
|
||||
task :release do
|
||||
Rake::Task['install:install'].invoke("Release")
|
||||
end
|
||||
|
||||
task :install, [:configuration] do |t, args|
|
||||
config = args[:configuration]
|
||||
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
|
||||
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
|
||||
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
|
||||
Rake::Task['build:build'].invoke(config)
|
||||
puts "Installing with configuration: #{config}"
|
||||
Rake::Task['remove_existing'].invoke()
|
||||
system "sudo cp -r #{xcodebuilddir}/#{config}/santa-driver.kext /Library/Extensions"
|
||||
system "sudo cp -r #{xcodebuilddir}/#{config}/Santa.app /Applications"
|
||||
end
|
||||
end
|
||||
|
||||
# Dist
|
||||
task :dist do
|
||||
desc "Create distribution folder"
|
||||
|
||||
Rake::Task['clean'].invoke()
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
|
||||
dist_path = "santa-#{`defaults read #{xcodebuilddir}/Release/santa-driver.kext/Contents/Info.plist CFBundleVersion`.strip}"
|
||||
|
||||
FileUtils.rm_rf(dist_path)
|
||||
|
||||
FileUtils.mkdir_p("#{dist_path}/binaries")
|
||||
FileUtils.mkdir_p("#{dist_path}/conf")
|
||||
FileUtils.mkdir_p("#{dist_path}/dsym")
|
||||
|
||||
BINARIES.each do |x|
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/binaries")
|
||||
end
|
||||
|
||||
DSYMS.each do |x|
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/dsym")
|
||||
end
|
||||
|
||||
|
||||
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{dist_path}/conf")}
|
||||
|
||||
puts "Distribution folder #{dist_path} created"
|
||||
end
|
||||
|
||||
# Tests
|
||||
namespace :tests do
|
||||
desc "Tests: Logic"
|
||||
task :logic => [:init] do
|
||||
puts "Running logic tests"
|
||||
xcodebuild("-scheme LogicTests test")
|
||||
end
|
||||
|
||||
desc "Tests: Kernel"
|
||||
task :kernel do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:debug'].invoke()
|
||||
Rake::Task['load_kext'].invoke
|
||||
FileUtils.mkdir_p("/tmp/santa_kerneltests_tmp")
|
||||
begin
|
||||
puts "\033[?25l\033[12h" # hide cursor
|
||||
puts "Running kernel tests"
|
||||
system "cd /tmp/santa_kerneltests_tmp && sudo #{xcodebuilddir}/Debug/KernelTests"
|
||||
rescue Exception
|
||||
ensure
|
||||
puts "\033[?25h\033[12l\n\n" # unhide cursor
|
||||
FileUtils.rm_rf("/tmp/santa_kerneltests_tmp")
|
||||
Rake::Task['unload_kext'].execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Load/Unload
|
||||
task :unload_daemon do
|
||||
puts "Unloading daemon"
|
||||
system "sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
task :unload_kext do
|
||||
puts "Unloading kernel extension"
|
||||
system "sudo kextunload -b com.google.santa-driver 2>/dev/null"
|
||||
end
|
||||
|
||||
task :unload_gui do
|
||||
puts "Unloading GUI agent"
|
||||
system "launchctl unload /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Unload"
|
||||
task :unload => [:unload_daemon, :unload_kext, :unload_gui]
|
||||
|
||||
task :load_daemon do
|
||||
puts "Loading daemon"
|
||||
system "sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist"
|
||||
end
|
||||
|
||||
task :load_kext do
|
||||
puts "Loading kernel extension"
|
||||
system "sudo kextload /Library/Extensions/santa-driver.kext"
|
||||
end
|
||||
|
||||
task :load_gui do
|
||||
puts "Loading GUI agent"
|
||||
system "launchctl load /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Load"
|
||||
task :load => [:load_kext, :load_daemon, :load_gui]
|
||||
|
||||
namespace :reload do
|
||||
desc "Reload: Debug"
|
||||
task :debug do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:debug'].invoke()
|
||||
Rake::Task['load'].invoke()
|
||||
end
|
||||
|
||||
desc "Reload: Release"
|
||||
task :release do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:release'].invoke()
|
||||
Rake::Task['load'].invoke()
|
||||
end
|
||||
end
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vulnerability, we would appreciate private disclosure
|
||||
so that we can work on a fix before disclosure. Any vulnerabilities reported to us will be
|
||||
disclosed publicly either when a new version with fixes is released or 90 days has passed,
|
||||
whichever comes first.
|
||||
|
||||
To report vulnerabilities to us privately, please e-mail `santa-team@google.com`.
|
||||
If you want to encrypt your e-mail, you can use our GPG key `0x92AFE41DAB49BBB6`
|
||||
available on pool.sks-keyservers.net:
|
||||
|
||||
`gpg --keyserver pool.sks-keyservers.net --recv-key 0x92AFE41DAB49BBB6`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
|
||||
BuildableName = "All"
|
||||
BlueprintName = "All"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
enableAddressSanitizer = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
|
||||
BuildableName = "All"
|
||||
BlueprintName = "All"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,91 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
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>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -14,7 +14,7 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
@@ -29,17 +29,6 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -55,14 +44,12 @@
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -74,7 +61,7 @@
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BlueprintIdentifier = "C779C2DD22F0E95000EE2541"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
|
||||
BuildableName = "santa-driver.kext"
|
||||
BlueprintName = "santa-driver"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
|
||||
BuildableName = "santa-driver.kext"
|
||||
BlueprintName = "santa-driver"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,91 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
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>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -14,9 +14,9 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -29,17 +29,6 @@
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -56,14 +45,12 @@
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -75,9 +62,9 @@
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
BlueprintIdentifier = "C779C4E522F0F51400EE2541"
|
||||
BuildableName = "com.google.santa.daemon"
|
||||
BlueprintName = "com.google.santa.daemon"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
|
||||
11
Santa.xcworkspace/contents.xcworkspacedata
generated
11
Santa.xcworkspace/contents.xcworkspacedata
generated
@@ -1 +1,10 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?><Workspace version='1.0'><FileRef location='group:Santa.xcodeproj'/><FileRef location='group:Pods/Pods.xcodeproj'/></Workspace>
|
||||
<?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,3 +0,0 @@
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
@@ -1,138 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTNotificationManager.h"
|
||||
|
||||
#import "SNTBlockMessage.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
/// The currently displayed notification
|
||||
@property SNTMessageWindowController *currentWindowController;
|
||||
|
||||
/// The queue of pending notifications
|
||||
@property(readonly) NSMutableArray *pendingNotifications;
|
||||
@end
|
||||
|
||||
@implementation SNTNotificationManager
|
||||
|
||||
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_pendingNotifications = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)windowDidCloseSilenceHash:(NSString *)hash {
|
||||
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
|
||||
|
||||
[self.pendingNotifications removeObject:self.currentWindowController];
|
||||
self.currentWindowController = nil;
|
||||
|
||||
if ([self.pendingNotifications count]) {
|
||||
self.currentWindowController = [self.pendingNotifications firstObject];
|
||||
[self.currentWindowController showWindow:self];
|
||||
} else {
|
||||
[NSApp hide:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
|
||||
if (!d) d = [NSMutableDictionary dictionary];
|
||||
if (date) {
|
||||
d[hash] = date;
|
||||
} else {
|
||||
[d removeObjectForKey:hash];
|
||||
}
|
||||
[ud setObject:d forKey:silencedNotificationsKey];
|
||||
}
|
||||
|
||||
#pragma mark SNTNotifierXPC protocol method
|
||||
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode {
|
||||
NSUserNotification *un = [[NSUserNotification alloc] init];
|
||||
un.title = @"Santa";
|
||||
un.hasActionButton = NO;
|
||||
NSString *customMsg;
|
||||
switch (clientmode) {
|
||||
case SNTClientModeMonitor:
|
||||
un.informativeText = @"Switching into Monitor mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
|
||||
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
if (customMsg.length) un.informativeText = customMsg;
|
||||
break;
|
||||
case SNTClientModeLockdown:
|
||||
un.informativeText = @"Switching into Lockdown mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
|
||||
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
if (customMsg.length) un.informativeText = customMsg;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
}
|
||||
|
||||
- (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];
|
||||
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
|
||||
|
||||
// See if this binary is silenced.
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
|
||||
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
|
||||
LOGI(@"Notification silence: date is in the future, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
|
||||
LOGI(@"Notification silence: date is more than one day ago, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
} else {
|
||||
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Notifications arrive on a background thread but UI updates must happen on the main thread.
|
||||
// This includes making windows.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SNTMessageWindowController *pendingMsg =
|
||||
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
// If a notification isn't currently being displayed, display the incoming one.
|
||||
if (!self.currentWindowController) {
|
||||
self.currentWindowController = pendingMsg;
|
||||
[pendingMsg showWindow:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
245
Source/common/BUILD
Normal file
245
Source/common/BUILD
Normal file
@@ -0,0 +1,245 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
srcs = ["SantaCache.h"],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SantaCacheTest",
|
||||
srcs = [
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage_SantaGUI",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCachedDecision",
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
hdrs = ["SNTCommonEnums.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTConfigurator",
|
||||
srcs = ["SNTConfigurator.m"],
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStrengthify",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDropRootPrivs",
|
||||
srcs = ["SNTDropRootPrivs.m"],
|
||||
hdrs = ["SNTDropRootPrivs.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTFileInfo",
|
||||
srcs = ["SNTFileInfo.m"],
|
||||
hdrs = ["SNTFileInfo.h"],
|
||||
deps = [
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTKernelCommon",
|
||||
hdrs = ["SNTKernelCommon.h"],
|
||||
)
|
||||
|
||||
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(
|
||||
name = "SNTPrefixTree",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = ["-std=c++11"],
|
||||
deps = [":SNTLogging"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTPrefixTreeKernel",
|
||||
srcs = ["SNTPrefixTree.cc"],
|
||||
hdrs = ["SNTPrefixTree.h"],
|
||||
copts = [
|
||||
"-std=c++11",
|
||||
"-mkernel",
|
||||
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
|
||||
],
|
||||
defines = ["KERNEL"],
|
||||
deps = [":SNTLoggingKernel"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
deps = [":SNTCommonEnums"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTStoredEvent",
|
||||
srcs = ["SNTStoredEvent.m"],
|
||||
hdrs = ["SNTStoredEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
"@MOLCertificate",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTStrengthify",
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSystemInfo",
|
||||
srcs = ["SNTSystemInfo.m"],
|
||||
hdrs = ["SNTSystemInfo.h"],
|
||||
sdk_frameworks = ["IOKit"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCBundleServiceInterface",
|
||||
srcs = ["SNTXPCBundleServiceInterface.m"],
|
||||
hdrs = ["SNTXPCBundleServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCControlInterface",
|
||||
srcs = ["SNTXPCControlInterface.m"],
|
||||
hdrs = ["SNTXPCControlInterface.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCUnprivilegedControlInterface",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCNotifierInterface",
|
||||
srcs = ["SNTXPCNotifierInterface.m"],
|
||||
hdrs = ["SNTXPCNotifierInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncdInterface",
|
||||
srcs = ["SNTXPCSyncdInterface.m"],
|
||||
hdrs = ["SNTXPCSyncdInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncServiceInterface",
|
||||
srcs = ["SNTXPCSyncServiceInterface.m"],
|
||||
hdrs = ["SNTXPCSyncServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCUnprivilegedControlInterface",
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTKernelCommon",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTFileInfoTest",
|
||||
srcs = ["SNTFileInfoTest.m"],
|
||||
resources = [
|
||||
"testdata/bad_pagezero",
|
||||
"testdata/missing_pagezero",
|
||||
"testdata/32bitplist",
|
||||
],
|
||||
structured_resources = glob([
|
||||
"testdata/BundleExample.app/**",
|
||||
"testdata/DirectoryBundle/**",
|
||||
]),
|
||||
deps = [":SNTFileInfo"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTPrefixTreeTest",
|
||||
srcs = ["SNTPrefixTreeTest.mm"],
|
||||
deps = ["SNTPrefixTree"],
|
||||
)
|
||||
@@ -12,6 +12,12 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifdef SANTAGUI
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@interface SNTBlockMessage : NSObject
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTBlockMessage.h"
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
@@ -26,10 +27,24 @@
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: #666;"
|
||||
@" 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>";
|
||||
|
||||
// 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"];
|
||||
htmlHeader = [NSString stringWithFormat:htmlHeader, dark ? @"#ddd" : @"#333"];
|
||||
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *message;
|
||||
@@ -51,7 +66,7 @@
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef NSAppKitVersionNumber10_0
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
@@ -92,18 +107,16 @@
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr;
|
||||
if (config.eventDetailBundleURL && event.fileBundleID) {
|
||||
formatStr = config.eventDetailBundleURL;
|
||||
} else {
|
||||
formatStr = config.eventDetailURL;
|
||||
}
|
||||
|
||||
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.fileSHA256];
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
@@ -113,13 +126,14 @@
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
if (event.fileBundleID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
|
||||
withString:event.fileBundleID];
|
||||
if (hostname.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%hostname%" withString:hostname];
|
||||
}
|
||||
if (event.fileBundleVersionString) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
|
||||
withString:event.fileBundleVersionString];
|
||||
if (uuid.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%uuid%" withString:uuid];
|
||||
}
|
||||
if (serial.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%" withString:serial];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
|
||||
@@ -12,19 +12,27 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
|
||||
@class MOLCertificate;
|
||||
|
||||
///
|
||||
/// Store information about executions from decision making for later logging.
|
||||
///
|
||||
@interface SNTCachedDecision : NSObject
|
||||
|
||||
@property uint64_t vnodeId;
|
||||
@property santa_vnode_id_t vnodeId;
|
||||
@property SNTEventState decision;
|
||||
@property NSString *decisionExtra;
|
||||
@property NSString *sha256;
|
||||
|
||||
@property NSString *certSHA256;
|
||||
@property NSString *certCommonName;
|
||||
@property NSArray<MOLCertificate *> *certChain;
|
||||
|
||||
@property NSString *quarantineURL;
|
||||
|
||||
@property NSString *customMsg;
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
|
||||
@implementation SNTCachedDecision
|
||||
@end
|
||||
@@ -12,6 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// These enums are used in various places throughout the Santa client code.
|
||||
/// The integer values are also stored in the database and so shouldn't be changed.
|
||||
@@ -27,10 +29,13 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
SNTRuleStateUnknown,
|
||||
|
||||
SNTRuleStateWhitelist = 1,
|
||||
SNTRuleStateBlacklist = 2,
|
||||
SNTRuleStateSilentBlacklist = 3,
|
||||
SNTRuleStateAllow = 1,
|
||||
SNTRuleStateBlock = 2,
|
||||
SNTRuleStateSilentBlock = 3,
|
||||
SNTRuleStateRemove = 4,
|
||||
|
||||
SNTRuleStateAllowCompiler = 5,
|
||||
SNTRuleStateAllowTransitive = 6,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
@@ -41,29 +46,52 @@ typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
SNTEventStateUnknown,
|
||||
// Bits 0-15 bits store non-decision types
|
||||
SNTEventStateUnknown = 0,
|
||||
SNTEventStateBundleBinary = 1,
|
||||
|
||||
SNTEventStateAllowUnknown = 1,
|
||||
SNTEventStateAllowBinary = 2,
|
||||
SNTEventStateAllowCertificate = 3,
|
||||
SNTEventStateAllowScope = 4,
|
||||
// Bits 16-23 store deny decision types
|
||||
SNTEventStateBlockUnknown = 1 << 16,
|
||||
SNTEventStateBlockBinary = 1 << 17,
|
||||
SNTEventStateBlockCertificate = 1 << 18,
|
||||
SNTEventStateBlockScope = 1 << 19,
|
||||
|
||||
SNTEventStateBlockUnknown = 5,
|
||||
SNTEventStateBlockBinary = 6,
|
||||
SNTEventStateBlockCertificate = 7,
|
||||
SNTEventStateBlockScope = 8,
|
||||
// Bits 24-31 store allow decision types
|
||||
SNTEventStateAllowUnknown = 1 << 24,
|
||||
SNTEventStateAllowBinary = 1 << 25,
|
||||
SNTEventStateAllowCertificate = 1 << 26,
|
||||
SNTEventStateAllowScope = 1 << 27,
|
||||
SNTEventStateAllowCompiler = 1 << 28,
|
||||
SNTEventStateAllowTransitive = 1 << 29,
|
||||
SNTEventStateAllowPendingTransitive = 1 << 30,
|
||||
|
||||
SNTEventStateBundleBinary = 9,
|
||||
// Block and Allow masks
|
||||
SNTEventStateBlock = 0xFF << 16,
|
||||
SNTEventStateAllow = 0xFF << 24
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
SNTRuleTableErrorEmptyRuleArray,
|
||||
SNTRuleTableErrorInsertOrReplaceFailed,
|
||||
SNTRuleTableErrorInvalidRule,
|
||||
SNTRuleTableErrorMissingRequiredRule,
|
||||
SNTRuleTableErrorRemoveFailed
|
||||
};
|
||||
|
||||
// This enum type is used to indicate what should be done with the related bundle events that are
|
||||
// generated when an initiating blocked bundle event occurs.
|
||||
typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
|
||||
SNTBundleEventActionDropEvents,
|
||||
SNTBundleEventActionStoreEvents,
|
||||
SNTBundleEventActionSendEvents,
|
||||
};
|
||||
|
||||
// Indicates where to store event logs.
|
||||
typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santad";
|
||||
static const char *kSantaCtlPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl";
|
||||
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";
|
||||
|
||||
@@ -12,23 +12,56 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Singleton that provides an interface for managing configuration values on disk
|
||||
/// @note This class is designed as a singleton but that is not strictly enforced.
|
||||
/// @note All properties are KVO compliant.
|
||||
///
|
||||
@interface SNTConfigurator : NSObject
|
||||
|
||||
/// Default config file path
|
||||
extern NSString *const kDefaultConfigFilePath;
|
||||
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// The operating mode.
|
||||
///
|
||||
@property(nonatomic) SNTClientMode clientMode;
|
||||
@property(readonly, nonatomic) SNTClientMode clientMode;
|
||||
|
||||
///
|
||||
/// Set the operating mode as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
|
||||
|
||||
///
|
||||
/// The regex of allowed paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *allowedPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of allowed paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerAllowedPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of blocked paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *blockedPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of blocked paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerBlockedPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
|
||||
@@ -37,25 +70,58 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *fileChangesRegex;
|
||||
@property(readonly, nonatomic) NSRegularExpression *fileChangesRegex;
|
||||
|
||||
///
|
||||
/// The regex of whitelisted paths. Regexes are specified in ICU format.
|
||||
/// A list of ignore prefixes which are checked in-kernel.
|
||||
/// This is more performant than FileChangesRegex when ignoring whole directory trees.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
/// For example adding a prefix of "/private/tmp/" will turn off file change log generation
|
||||
/// in-kernel for that entire tree. Since they are ignored by the kernel, they never reach santad
|
||||
/// and are not seen by the fileChangesRegex. Note the trailing "/", without it any file or
|
||||
/// directory starting with "/private/tmp" would be ignored.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
|
||||
|
||||
/// By default "/." and "/dev/" are added.
|
||||
///
|
||||
/// The regex of blacklisted paths. Regexes are specified in ICU format.
|
||||
/// Memory in the kernel is precious. A total of MAXPATHLEN (1024) nodes are allowed.
|
||||
/// Using all 1024 nodes will result in santa-driver allocating ~2MB of wired memory.
|
||||
/// An ASCII character uses 1 node. An UTF-8 encoded Unicode character uses 1-4 nodes.
|
||||
/// Prefixes are added to the running config in-order, one by one. The prefix will be ignored if
|
||||
/// (the running config's current size) + (the prefix's size) totals up to more than 1024 nodes.
|
||||
/// The running config is stored in a prefix tree.
|
||||
/// Prefixes that share prefixes are effectively de-duped; their shared node sized components only
|
||||
/// take up 1 node. For example these 3 prefixes all have a common prefix of "/private/".
|
||||
/// They will only take up 21 nodes instead of 39.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
/// "/private/tmp/"
|
||||
/// "/private/var/"
|
||||
/// "/private/new/"
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *blacklistPathRegex;
|
||||
/// -> [t] -> [m] -> [p] -> [/]
|
||||
///
|
||||
/// [/] -> [p] -> [r] -> [i] -> [v] -> [a] -> [t] -> [e] -> [/] -> [v] -> [a] -> [r] -> [/]
|
||||
///
|
||||
/// -> [n] -> [e] -> [w] -> [/]
|
||||
///
|
||||
/// Prefixes with Unicode characters work similarly. Assuming a UTF-8 encoding these two prefixes
|
||||
/// are actually the same for the first 3 nodes. They take up 7 nodes instead of 10.
|
||||
///
|
||||
/// "/🤘"
|
||||
/// "/🖖"
|
||||
///
|
||||
/// -> [0xa4] -> [0x98]
|
||||
///
|
||||
/// [/] -> [0xf0] -> [0x9f]
|
||||
///
|
||||
/// -> [0x96] -> [0x96]
|
||||
///
|
||||
/// To disable file change logging completely add "/".
|
||||
/// TODO(bur): Make this default if no FileChangesRegex is set.
|
||||
///
|
||||
/// Filters are only applied on santad startup.
|
||||
/// TODO(bur): Support add / remove of filters while santad is running.
|
||||
///
|
||||
@property(readonly, nonatomic) NSArray *fileChangesPrefixFilters;
|
||||
|
||||
///
|
||||
/// Enable __PAGEZERO protection, defaults to YES
|
||||
@@ -64,6 +130,58 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enablePageZeroProtection;
|
||||
|
||||
///
|
||||
/// Enable bad signature protection, defaults to NO.
|
||||
/// When enabled, a binary that is signed but has a bad signature (cert revoked, binary
|
||||
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary allowlist
|
||||
/// rule exists.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableBadSignatureProtection;
|
||||
|
||||
///
|
||||
/// Defines how event logs are stored. Options are:
|
||||
/// SNTEventLogTypeSyslog: Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog: Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTEventLogType eventLogType;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to Filelog, eventLogPath will provide the path to save logs.
|
||||
/// Defaults to /var/db/santa/santa.log.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
|
||||
/// has been overriden, this is the host's UUID.
|
||||
/// Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
@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
|
||||
|
||||
///
|
||||
@@ -75,8 +193,6 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
/// When the user gets a block notification, a button can be displayed which will
|
||||
/// take them to a web page with more information about that event.
|
||||
/// There are two properties, one for individual binaries and one for binaries that are part
|
||||
/// of a bundle. If the latter is not set the former will be used.
|
||||
///
|
||||
/// This property contains a kind of format string to be turned into the URL to send them to.
|
||||
/// The following sequences will be replaced in the final URL:
|
||||
@@ -84,15 +200,15 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
/// %file_sha% -- SHA-256 of the file that was blocked.
|
||||
/// %machine_id% -- ID of the machine.
|
||||
/// %username% -- executing user.
|
||||
/// %bundle_id% -- bundle id of the binary, if applicable.
|
||||
/// %bundle_ver% -- bundle version of the binary, if applicable.
|
||||
/// %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.
|
||||
///
|
||||
/// If this item isn't set, the Open Event button will not be displayed.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventDetailURL;
|
||||
@property(readonly, nonatomic) NSString *eventDetailBundleURL;
|
||||
|
||||
///
|
||||
/// Related to the above property, this string represents the text to show on the button.
|
||||
@@ -131,21 +247,20 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *syncBaseURL;
|
||||
|
||||
///
|
||||
/// If YES, mid-execution event uploads are skipped.
|
||||
/// This property is never stored on disk.
|
||||
///
|
||||
@property BOOL syncBackOff;
|
||||
|
||||
///
|
||||
/// The machine owner.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *machineOwner;
|
||||
|
||||
///
|
||||
/// The last date of successful sync.
|
||||
/// The last date of a successful full sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *syncLastSuccess;
|
||||
@property(nonatomic) NSDate *fullSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// The last date of a successful rule sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *ruleSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// If YES a clean sync is required.
|
||||
@@ -157,6 +272,21 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *machineID;
|
||||
|
||||
///
|
||||
/// If YES, enables bundle detection for blocked events. This property is not stored on disk.
|
||||
/// Its value is set by a sync server that supports bundles. Defaults to NO.
|
||||
///
|
||||
@property BOOL enableBundles;
|
||||
|
||||
#pragma mark Transitive Allowlist Settings
|
||||
|
||||
///
|
||||
/// If YES, binaries marked with SNTRuleStateAllowCompiler rules are allowed to transitively
|
||||
/// allow any executables that they produce. If NO, SNTRuleStateAllowCompiler rules are
|
||||
/// interpreted as if they were simply SNTRuleStateAllow rules. Defaults to NO.
|
||||
///
|
||||
@property BOOL enableTransitiveRules;
|
||||
|
||||
#pragma mark Server Auth Settings
|
||||
|
||||
///
|
||||
@@ -194,21 +324,39 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *syncClientAuthCertificateIssuer;
|
||||
|
||||
///
|
||||
/// If true, forks and exits will be logged. Defaults to false.
|
||||
///
|
||||
@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;
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
///
|
||||
+ (instancetype)configurator;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
/// Clear the sync server configuration from the effective configuration.
|
||||
///
|
||||
/// @param filePath The path to the file to use as a backing store.
|
||||
///
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath;
|
||||
|
||||
///
|
||||
/// Re-read config data from disk.
|
||||
///
|
||||
- (void)reloadConfigData;
|
||||
- (void)clearSyncState;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,48 +12,39 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTSystemInfo.h"
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@interface SNTConfigurator ()
|
||||
@property NSString *configFilePath;
|
||||
@property NSMutableDictionary *configData;
|
||||
/// A NSUserDefaults object set to use the com.google.santa suite.
|
||||
@property(readonly, nonatomic) NSUserDefaults *defaults;
|
||||
|
||||
/// Creating NSRegularExpression objects is not fast, so cache them.
|
||||
@property NSRegularExpression *cachedFileChangesRegex;
|
||||
@property NSRegularExpression *cachedWhitelistDirRegex;
|
||||
@property NSRegularExpression *cachedBlacklistDirRegex;
|
||||
/// Keys and expected value types.
|
||||
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
|
||||
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
|
||||
|
||||
/// Array of keys that cannot be changed while santad is running if santad didn't make the change.
|
||||
@property(readonly) NSArray *protectedKeys;
|
||||
/// 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
|
||||
|
||||
/// The hard-coded path to the config file
|
||||
NSString *const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
/// The hard-coded path to the sync state file.
|
||||
NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
|
||||
|
||||
/// The keys in the config file
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
|
||||
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
|
||||
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
|
||||
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailBundleURLKey = @"EventDetailBundleURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
/// The domain used by mobileconfig.
|
||||
static NSString *const kMobileConfigDomain = @"com.google.santa";
|
||||
|
||||
/// The keys managed by a mobileconfig.
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kSyncLastSuccess = @"SyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
@@ -63,18 +54,122 @@ static NSString *const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
|
||||
|
||||
static NSString *const kMachineOwnerKey = @"MachineOwner";
|
||||
static NSString *const kMachineIDKey = @"MachineID";
|
||||
|
||||
static NSString *const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
|
||||
static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
|
||||
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath {
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
|
||||
static NSString *const kEnableBadSignatureProtectionKey = @"EnableBadSignatureProtection";
|
||||
|
||||
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
|
||||
static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters";
|
||||
|
||||
static NSString *const kEventLogType = @"EventLogType";
|
||||
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";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
|
||||
static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting";
|
||||
static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
|
||||
static NSString *const kAllowedPathRegexKeyDeprecated = @"WhitelistRegex";
|
||||
static NSString *const kBlockedPathRegexKey = @"BlockedPathRegex";
|
||||
static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
|
||||
|
||||
// The keys managed by a sync server.
|
||||
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
|
||||
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_configFilePath = filePath;
|
||||
[self reloadConfigData];
|
||||
Class number = [NSNumber class];
|
||||
Class re = [NSRegularExpression class];
|
||||
Class date = [NSDate class];
|
||||
Class string = [NSString class];
|
||||
Class data = [NSData class];
|
||||
Class array = [NSArray class];
|
||||
_syncServerKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kEnableTransitiveRulesKey : number,
|
||||
kEnableTransitiveRulesKeyDeprecated : number,
|
||||
kAllowedPathRegexKey : re,
|
||||
kAllowedPathRegexKeyDeprecated : re,
|
||||
kBlockedPathRegexKey : re,
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
};
|
||||
_forcedConfigKeyTypes = @{
|
||||
kClientModeKey : number,
|
||||
kEnableTransitiveRulesKey : number,
|
||||
kEnableTransitiveRulesKeyDeprecated : number,
|
||||
kFileChangesRegexKey : re,
|
||||
kFileChangesPrefixFiltersKey : array,
|
||||
kAllowedPathRegexKey : re,
|
||||
kAllowedPathRegexKeyDeprecated : re,
|
||||
kBlockedPathRegexKey : re,
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kSyncBaseURLKey : string,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
kClientAuthCertificateIssuerKey : string,
|
||||
kServerAuthRootsDataKey : data,
|
||||
kServerAuthRootsFileKey : string,
|
||||
kMachineOwnerKey : string,
|
||||
kMachineIDKey : string,
|
||||
kMachineOwnerPlistFileKey : string,
|
||||
kMachineOwnerPlistKeyKey : string,
|
||||
kMachineIDPlistFileKey : string,
|
||||
kMachineIDPlistKeyKey : string,
|
||||
kEventLogType : string,
|
||||
kEventLogPath : string,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSystemExtension : number,
|
||||
kEnableSysxCache : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
kEnableDebugLogging : number,
|
||||
kEnableBackwardsCompatibleContentEncoding : number,
|
||||
};
|
||||
_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;
|
||||
}
|
||||
@@ -82,291 +177,548 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
#pragma mark Singleton retriever
|
||||
|
||||
+ (instancetype)configurator {
|
||||
static SNTConfigurator *sharedConfigurator = nil;
|
||||
static SNTConfigurator *sharedConfigurator;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
|
||||
sharedConfigurator = [[SNTConfigurator alloc] init];
|
||||
});
|
||||
return sharedConfigurator;
|
||||
}
|
||||
|
||||
#pragma mark Protected Keys
|
||||
+ (NSSet *)syncAndConfigStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [[self syncStateSet] setByAddingObjectsFromSet:[self configStateSet]];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
- (NSArray *)protectedKeys {
|
||||
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey,
|
||||
kFileChangesRegexKey, kSyncBaseURLKey ];
|
||||
+ (NSSet *)syncStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithObject:NSStringFromSelector(@selector(syncState))];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
+ (NSSet *)configStateSet {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithObject:NSStringFromSelector(@selector(configState))];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
#pragma mark KVO Dependencies
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingClientMode {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingAllowlistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBlocklistPathRegex {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileChangesRegex {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFileChangesPrefixFiltersKey {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventDetailURL {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventDetailText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingBannedBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingModeNotificationMonitor {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateFile {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificatePassword {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateCn {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateIssuer {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsData {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsFile {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMachineOwner {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingMachineID {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingRuleSyncLastSuccess {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncCleanRequired {
|
||||
return [self syncStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventLogType {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEventLogPath {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableMachineIDDecoration {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableTransitiveRules {
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSystemExtension {
|
||||
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];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
NSInteger cm = [self.configData[kClientModeKey] longValue];
|
||||
SNTClientMode cm = [self.syncState[kClientModeKey] longLongValue];
|
||||
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
|
||||
return (SNTClientMode)cm;
|
||||
} else {
|
||||
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
|
||||
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
|
||||
return SNTClientModeMonitor;
|
||||
return cm;
|
||||
}
|
||||
|
||||
cm = [self.configState[kClientModeKey] longLongValue];
|
||||
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
|
||||
return cm;
|
||||
}
|
||||
|
||||
return SNTClientModeMonitor;
|
||||
}
|
||||
|
||||
- (void)setClientMode:(SNTClientMode)newMode {
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
|
||||
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
|
||||
self.configData[kClientModeKey] = @(newMode);
|
||||
[self saveConfigToDisk];
|
||||
} else {
|
||||
LOGW(@"Ignoring request to change client mode to %ld", newMode);
|
||||
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)whitelistPathRegex {
|
||||
if (!self.cachedWhitelistDirRegex && self.configData[kWhitelistRegexKey]) {
|
||||
NSString *re = self.configData[kWhitelistRegexKey];
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:NULL];
|
||||
}
|
||||
return self.cachedWhitelistDirRegex;
|
||||
- (BOOL)enableTransitiveRules {
|
||||
NSNumber *n = self.syncState[kEnableTransitiveRulesKey];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
n = self.syncState[kEnableTransitiveRulesKeyDeprecated];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
n = self.configState[kEnableTransitiveRulesKeyDeprecated];
|
||||
if (n) return [n boolValue];
|
||||
|
||||
return [self.configState[kEnableTransitiveRulesKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kWhitelistRegexKey];
|
||||
} else {
|
||||
self.configData[kWhitelistRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedWhitelistDirRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
- (void)setEnableTransitiveRules:(BOOL)enabled {
|
||||
[self updateSyncStateForKey:kEnableTransitiveRulesKey value:@(enabled)];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)blacklistPathRegex {
|
||||
if (!self.cachedBlacklistDirRegex && self.configData[kBlacklistRegexKey]) {
|
||||
NSString *re = self.configData[kBlacklistRegexKey];
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedBlacklistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:NULL];
|
||||
}
|
||||
return self.cachedBlacklistDirRegex;
|
||||
- (NSRegularExpression *)allowedPathRegex {
|
||||
NSRegularExpression *r = self.syncState[kAllowedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
r = self.syncState[kAllowedPathRegexKeyDeprecated];
|
||||
if (r) return r;
|
||||
|
||||
r = self.configState[kAllowedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
return self.configState[kAllowedPathRegexKeyDeprecated];
|
||||
}
|
||||
|
||||
- (void)setBlacklistPathRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kBlacklistRegexKey];
|
||||
} else {
|
||||
self.configData[kBlacklistRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedBlacklistDirRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
- (void)setSyncServerAllowedPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kAllowedPathRegexKey value:re];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)blockedPathRegex {
|
||||
NSRegularExpression *r = self.syncState[kBlockedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
r = self.syncState[kBlockedPathRegexKeyDeprecated];
|
||||
if (r) return r;
|
||||
|
||||
r = self.configState[kBlockedPathRegexKey];
|
||||
if (r) return r;
|
||||
|
||||
return self.configState[kBlockedPathRegexKeyDeprecated];
|
||||
}
|
||||
|
||||
- (void)setSyncServerBlockedPathRegex:(NSRegularExpression *)re {
|
||||
[self updateSyncStateForKey:kBlockedPathRegexKey value:re];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)fileChangesRegex {
|
||||
if (!self.cachedFileChangesRegex && self.configData[kFileChangesRegexKey]) {
|
||||
NSString *re = self.configData[kFileChangesRegexKey];
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedFileChangesRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:NULL];
|
||||
return self.configState[kFileChangesRegexKey];
|
||||
}
|
||||
|
||||
- (NSArray *)fileChangesPrefixFilters {
|
||||
NSArray *filters = self.configState[kFileChangesPrefixFiltersKey];
|
||||
for (id filter in filters) {
|
||||
if (![filter isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self.cachedFileChangesRegex;
|
||||
}
|
||||
|
||||
- (void)setFileChangesRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kFileChangesRegexKey];
|
||||
} else {
|
||||
self.configData[kFileChangesRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedFileChangesRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (BOOL)enablePageZeroProtection {
|
||||
NSNumber *keyValue = self.configData[kEnablePageZeroProtectionKey];
|
||||
return keyValue ? [keyValue boolValue] : YES;
|
||||
}
|
||||
|
||||
- (NSURL *)moreInfoURL {
|
||||
return [NSURL URLWithString:self.configData[kMoreInfoURLKey]];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailURL {
|
||||
return self.configData[kEventDetailURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailBundleURL {
|
||||
return self.configData[kEventDetailBundleURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailText {
|
||||
return self.configData[kEventDetailTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)unknownBlockMessage {
|
||||
return self.configData[kUnknownBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)bannedBlockMessage {
|
||||
return self.configData[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configData[kModeNotificationMonitor];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationLockdown {
|
||||
return self.configData[kModeNotificationLockdown];
|
||||
return filters;
|
||||
}
|
||||
|
||||
- (NSURL *)syncBaseURL {
|
||||
NSString *urlStr = self.configData[kSyncBaseURLKey];
|
||||
if (urlStr) {
|
||||
NSURL *url = [NSURL URLWithString:urlStr];
|
||||
if (!url) LOGW(@"SyncBaseURL is not a valid URL!");
|
||||
return url;
|
||||
}
|
||||
return nil;
|
||||
NSString *urlString = self.configState[kSyncBaseURLKey];
|
||||
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
return url;
|
||||
}
|
||||
|
||||
- (BOOL)enablePageZeroProtection {
|
||||
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
|
||||
return number ? [number boolValue] : YES;
|
||||
}
|
||||
|
||||
- (BOOL)enableBadSignatureProtection {
|
||||
NSNumber *number = self.configState[kEnableBadSignatureProtectionKey];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (NSURL *)moreInfoURL {
|
||||
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailURL {
|
||||
return self.configState[kEventDetailURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailText {
|
||||
return self.configState[kEventDetailTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)unknownBlockMessage {
|
||||
return self.configState[kUnknownBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)bannedBlockMessage {
|
||||
return self.configState[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configState[kModeNotificationMonitor];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationLockdown {
|
||||
return self.configState[kModeNotificationLockdown];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateFile {
|
||||
return self.configData[kClientAuthCertificateFileKey];
|
||||
return self.configState[kClientAuthCertificateFileKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificatePassword {
|
||||
return self.configData[kClientAuthCertificatePasswordKey];
|
||||
return self.configState[kClientAuthCertificatePasswordKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateCn {
|
||||
return self.configData[kClientAuthCertificateCNKey];
|
||||
return self.configState[kClientAuthCertificateCNKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateIssuer {
|
||||
return self.configData[kClientAuthCertificateIssuerKey];
|
||||
return self.configState[kClientAuthCertificateIssuerKey];
|
||||
}
|
||||
|
||||
- (NSData *)syncServerAuthRootsData {
|
||||
return self.configData[kServerAuthRootsDataKey];
|
||||
return self.configState[kServerAuthRootsDataKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncServerAuthRootsFile {
|
||||
return self.configData[kServerAuthRootsFileKey];
|
||||
return self.configState[kServerAuthRootsFileKey];
|
||||
}
|
||||
|
||||
- (NSDate *)syncLastSuccess {
|
||||
return self.configData[kSyncLastSuccess];
|
||||
- (NSDate *)fullSyncLastSuccess {
|
||||
return self.syncState[kFullSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setSyncLastSuccess:(NSDate *)syncLastSuccess {
|
||||
self.configData[kSyncLastSuccess] = syncLastSuccess;
|
||||
[self saveConfigToDisk];
|
||||
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
|
||||
[self updateSyncStateForKey:kFullSyncLastSuccess value:fullSyncLastSuccess];
|
||||
self.ruleSyncLastSuccess = fullSyncLastSuccess;
|
||||
}
|
||||
|
||||
- (NSDate *)ruleSyncLastSuccess {
|
||||
return self.syncState[kRuleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
|
||||
[self updateSyncStateForKey:kRuleSyncLastSuccess value:ruleSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (BOOL)syncCleanRequired {
|
||||
return [self.configData[kSyncCleanRequired] boolValue];
|
||||
return [self.syncState[kSyncCleanRequired] boolValue];
|
||||
}
|
||||
|
||||
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
|
||||
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
|
||||
[self saveConfigToDisk];
|
||||
[self updateSyncStateForKey:kSyncCleanRequired value:@(syncCleanRequired)];
|
||||
}
|
||||
|
||||
- (NSString *)machineOwner {
|
||||
NSString *machineOwner;
|
||||
NSString *machineOwner = self.configState[kMachineOwnerKey];
|
||||
if (machineOwner) return machineOwner;
|
||||
|
||||
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
|
||||
NSDictionary *plist =
|
||||
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
|
||||
machineOwner = plist[self.configData[kMachineOwnerPlistKeyKey]];
|
||||
NSString *plistPath = self.configState[kMachineOwnerPlistFileKey];
|
||||
NSString *plistKey = self.configState[kMachineOwnerPlistKeyKey];
|
||||
if (plistPath && plistKey) {
|
||||
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||
machineOwner = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
|
||||
}
|
||||
|
||||
if (self.configData[kMachineOwnerKey]) {
|
||||
machineOwner = self.configData[kMachineOwnerKey];
|
||||
}
|
||||
|
||||
if (!machineOwner) machineOwner = @"";
|
||||
|
||||
return machineOwner;
|
||||
return machineOwner ?: @"";
|
||||
}
|
||||
|
||||
- (NSString *)machineID {
|
||||
NSString *machineId;
|
||||
NSString *machineId = self.configState[kMachineIDKey];
|
||||
if (machineId) return machineId;
|
||||
|
||||
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
|
||||
NSDictionary *plist =
|
||||
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
|
||||
machineId = plist[self.configData[kMachineIDPlistKeyKey]];
|
||||
NSString *plistPath = self.configState[kMachineIDPlistFileKey];
|
||||
NSString *plistKey = self.configState[kMachineIDPlistKeyKey];
|
||||
|
||||
if (plistPath && plistKey) {
|
||||
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||
machineId = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
|
||||
}
|
||||
|
||||
if (self.configData[kMachineIDKey]) {
|
||||
machineId = self.configData[kMachineIDKey];
|
||||
}
|
||||
|
||||
if ([machineId length] == 0) {
|
||||
machineId = [SNTSystemInfo hardwareUUID];
|
||||
}
|
||||
|
||||
return machineId;
|
||||
return machineId.length ? machineId : [SNTSystemInfo hardwareUUID];
|
||||
}
|
||||
|
||||
- (void)reloadConfigData {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:self.configFilePath]) return;
|
||||
- (SNTEventLogType)eventLogType {
|
||||
NSString *s = [self.configState[kEventLogType] lowercaseString];
|
||||
return [s isEqualToString:@"syslog"] ? SNTEventLogTypeSyslog : SNTEventLogTypeFilelog;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not read configuration file: %@, replacing.", [error localizedDescription]);
|
||||
[self saveConfigToDisk];
|
||||
return;
|
||||
}
|
||||
- (NSString *)eventLogPath {
|
||||
return self.configState[kEventLogPath] ?: @"/var/db/santa/santa.log";
|
||||
}
|
||||
|
||||
NSMutableDictionary *configData =
|
||||
[NSPropertyListSerialization propertyListWithData:readData
|
||||
options:NSPropertyListMutableContainers
|
||||
format:NULL
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not parse configuration file: %@, replacing.", [error localizedDescription]);
|
||||
[self saveConfigToDisk];
|
||||
return;
|
||||
}
|
||||
- (BOOL)enableMachineIDDecoration {
|
||||
NSNumber *number = self.configState[kEnableMachineIDDecoration];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
if (self.syncBaseURL) {
|
||||
// Ensure no-one is trying to change protected keys behind our back.
|
||||
BOOL changed = NO;
|
||||
if (geteuid() == 0) {
|
||||
for (NSString *key in self.protectedKeys) {
|
||||
if (((self.configData[key] && !configData[key]) ||
|
||||
(!self.configData[key] && configData[key]) ||
|
||||
(self.configData[key] && ![self.configData[key] isEqual:configData[key]]))) {
|
||||
if (self.configData[key]) {
|
||||
configData[key] = self.configData[key];
|
||||
} else {
|
||||
[configData removeObjectForKey:key];
|
||||
}
|
||||
changed = YES;
|
||||
LOGI(@"Ignoring changed configuration key: %@", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.configData = configData;
|
||||
if (changed) [self saveConfigToDisk];
|
||||
- (BOOL)enableSystemExtension {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:@"/Library/Extensions/santa-driver.kext"]) return YES;
|
||||
NSNumber *number = self.configState[kEnableSystemExtension];
|
||||
return number ? [number boolValue] : YES;
|
||||
} else {
|
||||
self.configData = configData;
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
///
|
||||
/// Saves the current @c self.configData to disk.
|
||||
/// Update the syncState. Triggers a KVO event for all dependents.
|
||||
///
|
||||
- (void)saveConfigToDisk {
|
||||
- (void)updateSyncStateForKey:(NSString *)key value:(id)value {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
syncState[key] = value;
|
||||
self.syncState = syncState;
|
||||
[self saveSyncStateToDisk];
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
/// Read the saved syncState.
|
||||
///
|
||||
- (NSMutableDictionary *)readSyncStateFromDisk {
|
||||
// Only read the sync state if a sync server is configured.
|
||||
if (!self.syncBaseURL) return nil;
|
||||
// Only santad should read this file.
|
||||
if (geteuid() != 0) return nil;
|
||||
NSMutableDictionary *syncState =
|
||||
[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;
|
||||
syncState[key] = [self expressionForPattern:pattern];
|
||||
} else if (![syncState[key] isKindOfClass:self.syncServerKeyTypes[key]]) {
|
||||
syncState[key] = nil;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return syncState;
|
||||
}
|
||||
|
||||
///
|
||||
/// Saves the current effective syncState to disk.
|
||||
///
|
||||
- (void)saveSyncStateToDisk {
|
||||
// Only save the sync state if a sync server is configured.
|
||||
if (!self.syncBaseURL) return;
|
||||
// Only santad should write to this file.
|
||||
if (geteuid() != 0) return;
|
||||
[self.configData writeToFile:self.configFilePath atomically:YES];
|
||||
// Either remove
|
||||
NSMutableDictionary *syncState = self.syncState.mutableCopy;
|
||||
syncState[kAllowedPathRegexKey] = [syncState[kAllowedPathRegexKey] pattern];
|
||||
syncState[kBlockedPathRegexKey] = [syncState[kBlockedPathRegexKey] pattern];
|
||||
[syncState writeToFile:kSyncStateFilePath atomically:YES];
|
||||
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
|
||||
ofItemAtPath:kSyncStateFilePath error:NULL];
|
||||
}
|
||||
|
||||
- (void)clearSyncState {
|
||||
self.syncState = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
#pragma mark Private Defaults Methods
|
||||
|
||||
- (NSRegularExpression *)expressionForPattern:(NSString *)pattern {
|
||||
if (!pattern) return nil;
|
||||
if (![pattern hasPrefix:@"^"]) pattern = [@"^" stringByAppendingString:pattern];
|
||||
return [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)readForcedConfig {
|
||||
NSMutableDictionary *forcedConfig = [NSMutableDictionary dictionary];
|
||||
for (NSString *key in self.forcedConfigKeyTypes) {
|
||||
id obj = [self forcedConfigValueForKey:key];
|
||||
forcedConfig[key] = [obj isKindOfClass:self.forcedConfigKeyTypes[key]] ? obj : nil;
|
||||
// Create the regex objects now
|
||||
if (self.forcedConfigKeyTypes[key] == [NSRegularExpression class]) {
|
||||
NSString *pattern = [obj isKindOfClass:[NSString class]] ? obj : nil;
|
||||
forcedConfig[key] = [self expressionForPattern:pattern];
|
||||
}
|
||||
}
|
||||
return forcedConfig;
|
||||
}
|
||||
|
||||
- (id)forcedConfigValueForKey:(NSString *)key {
|
||||
id obj = [self.defaults objectForKey:key];
|
||||
return [self.defaults objectIsForcedForKey:key inDomain:kMobileConfigDomain] ? obj : nil;
|
||||
}
|
||||
|
||||
- (void)startWatchingDefaults {
|
||||
// Only com.google.santa.daemon should listen.
|
||||
NSString *processName = [[NSProcessInfo processInfo] processName];
|
||||
if (![processName isEqualToString:@"com.google.santa.daemon"]) return;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(defaultsChanged:)
|
||||
name:NSUserDefaultsDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)defaultsChanged:(void *)v {
|
||||
SEL handleChange = @selector(handleChange);
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:handleChange object:nil];
|
||||
[self performSelector:handleChange withObject:nil afterDelay:5.0f];
|
||||
}
|
||||
|
||||
///
|
||||
/// Update the configState. Triggers a KVO event for all dependents.
|
||||
///
|
||||
- (void)handleChange {
|
||||
self.configState = [self readForcedConfig];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// Simple function to check and drop root privileges.
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "Source/common/SNTDropRootPrivs.h"
|
||||
|
||||
BOOL DropRootPrivileges() {
|
||||
if (getuid() == 0 || geteuid() == 0 || getgid() == 0 || getegid() == 0) {
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
|
||||
///
|
||||
/// Represents a binary on disk, providing access to details about that binary
|
||||
/// such as the SHA-1, SHA-256, Info.plist and the Mach-O data.
|
||||
@@ -36,6 +40,18 @@
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
|
||||
///
|
||||
/// Initializer for already resolved paths.
|
||||
///
|
||||
/// @param path The path of the file this instance is to represent. The path will
|
||||
/// not be converted and will be used as is. If the path is not a regular file this method will
|
||||
/// return nil and fill in an error.
|
||||
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
|
||||
/// describing the problem.
|
||||
///
|
||||
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// @return Path of this file.
|
||||
///
|
||||
@@ -84,6 +100,11 @@
|
||||
///
|
||||
- (BOOL)isDylib;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a bundle executable (QuickLook/Spotlight plugin, etc.)
|
||||
///
|
||||
- (BOOL)isBundle;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a kernel extension.
|
||||
///
|
||||
@@ -104,11 +125,31 @@
|
||||
///
|
||||
- (BOOL)isDMG;
|
||||
|
||||
///
|
||||
/// @return NSString describing the kind of file (executable, bundle, script, etc.)
|
||||
///
|
||||
- (NSString *)humanReadableFileType;
|
||||
|
||||
///
|
||||
/// @return YES if this file has a bad/missing __PAGEZERO .
|
||||
///
|
||||
- (BOOL)isMissingPageZero;
|
||||
|
||||
///
|
||||
/// If set to YES, the bundle* and infoPlist methods will search for and use the highest NSBundle
|
||||
/// found in the tree. Defaults to NO, which uses the first found bundle, if any.
|
||||
///
|
||||
/// @example:
|
||||
/// An SNTFileInfo object that represents
|
||||
/// /Applications/Photos.app/Contents/XPCServices/com.apple.Photos.librarychooserservice.xpc
|
||||
/// useAncestorBundle is set to YES
|
||||
/// /Applications/Photos.app will be used to get data backing all the bundle methods
|
||||
///
|
||||
/// @note: The NSBundle object backing the bundle* and infoPlist methods is cached once found.
|
||||
/// Setting the useAncestorBundle propery will clear this cache and force a re-search.
|
||||
///
|
||||
@property(nonatomic) BOOL useAncestorBundle;
|
||||
|
||||
///
|
||||
/// @return An NSBundle if this file is part of a bundle.
|
||||
///
|
||||
@@ -171,4 +212,16 @@
|
||||
///
|
||||
- (NSUInteger)fileSize;
|
||||
|
||||
///
|
||||
/// @return The underlying file handle.
|
||||
///
|
||||
@property(readonly) NSFileHandle *fileHandle;
|
||||
|
||||
///
|
||||
/// @return Returns an instance of MOLCodeSignChecker initialized with the file's binary path.
|
||||
/// Both the MOLCodesignChecker and any resulting NSError are cached and returned on subsequent
|
||||
/// calls. You may pass in NULL for the error if you don't care to receive it.
|
||||
///
|
||||
- (MOLCodesignChecker *)codesignCheckerWithError:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,17 +12,19 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTFileInfo.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <fmdb/FMDB.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#import <FMDB/FMDB.h>
|
||||
|
||||
// Simple class to hold the data of a mach_header and the offset within the file
|
||||
// in which that header was found.
|
||||
@@ -53,29 +55,51 @@
|
||||
@property NSDictionary *infoDict;
|
||||
@property NSDictionary *quarantineDict;
|
||||
@property NSDictionary *cachedHeaders;
|
||||
@property MOLCodesignChecker *cachedCodesignChecker;
|
||||
@property(nonatomic) NSError *codesignCheckerError;
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfo
|
||||
|
||||
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
|
||||
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSBundle *bndl;
|
||||
_path = [self resolvePath:path bundle:&bndl];
|
||||
_bundleRef = bndl;
|
||||
if (_path.length == 0) {
|
||||
_path = path;
|
||||
if (!_path.length) {
|
||||
if (error) {
|
||||
NSString *errStr = @"Unable to resolve empty path";
|
||||
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
|
||||
NSString *errStr = @"Unable to use empty path";
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:260
|
||||
code:270
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
struct stat fileStat;
|
||||
lstat(_path.UTF8String, &fileStat);
|
||||
if (!((S_IFMT & fileStat.st_mode) == S_IFREG)) {
|
||||
if (error) {
|
||||
NSString *errStr = [NSString stringWithFormat:@"Non regular file: %s", strerror(errno)];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:290
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
_fileSize = fileStat.st_size;
|
||||
|
||||
if (_fileSize == 0) return nil;
|
||||
|
||||
if (fileStat.st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat.st_uid);
|
||||
if (pwd) {
|
||||
_fileOwnerHomeDir = @(pwd->pw_dir);
|
||||
}
|
||||
}
|
||||
|
||||
int fd = open([_path UTF8String], O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
if (error) {
|
||||
@@ -87,24 +111,29 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return nil;
|
||||
}
|
||||
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
|
||||
|
||||
struct stat fileStat;
|
||||
fstat(_fileHandle.fileDescriptor, &fileStat);
|
||||
_fileSize = fileStat.st_size;
|
||||
|
||||
if (_fileSize == 0) return nil;
|
||||
|
||||
if (fileStat.st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat.st_uid);
|
||||
if (pwd) {
|
||||
_fileOwnerHomeDir = @(pwd->pw_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
|
||||
NSBundle *bndl;
|
||||
NSString *resolvedPath = [self resolvePath:path bundle:&bndl];
|
||||
if (!resolvedPath.length) {
|
||||
if (error) {
|
||||
NSString *errStr = @"Unable to resolve empty path";
|
||||
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:260
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
self = [self initWithResolvedPath:resolvedPath error:error];
|
||||
if (self && bndl) _bundleRef = bndl;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
return [self initWithPath:path error:NULL];
|
||||
}
|
||||
@@ -114,67 +143,71 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
|
||||
const int MAX_CHUNK_SIZE = 256 * 1024; // 256 KB
|
||||
const size_t chunkSize = _fileSize > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : _fileSize;
|
||||
char chunk[chunkSize];
|
||||
char *chunk = malloc(chunkSize);
|
||||
|
||||
CC_SHA1_CTX c1;
|
||||
CC_SHA256_CTX c256;
|
||||
@try {
|
||||
CC_SHA1_CTX c1;
|
||||
CC_SHA256_CTX c256;
|
||||
|
||||
if (sha1) CC_SHA1_Init(&c1);
|
||||
if (sha256) CC_SHA256_Init(&c256);
|
||||
if (sha1) CC_SHA1_Init(&c1);
|
||||
if (sha256) CC_SHA256_Init(&c256);
|
||||
|
||||
int fd = self.fileHandle.fileDescriptor;
|
||||
int fd = self.fileHandle.fileDescriptor;
|
||||
|
||||
fcntl(fd, F_RDAHEAD, 1);
|
||||
struct radvisory radv;
|
||||
radv.ra_offset = 0;
|
||||
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
|
||||
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
|
||||
fcntl(fd, F_RDADVISE, &radv);
|
||||
ssize_t bytesRead;
|
||||
fcntl(fd, F_RDAHEAD, 1);
|
||||
struct radvisory radv;
|
||||
radv.ra_offset = 0;
|
||||
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
|
||||
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
|
||||
fcntl(fd, F_RDADVISE, &radv);
|
||||
ssize_t bytesRead;
|
||||
|
||||
for (uint64_t offset = 0; offset < _fileSize;) {
|
||||
bytesRead = pread(fd, chunk, chunkSize, offset);
|
||||
if (bytesRead > 0) {
|
||||
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
|
||||
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
|
||||
offset += bytesRead;
|
||||
} else if (bytesRead == -1 && errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
return;
|
||||
for (uint64_t offset = 0; offset < _fileSize;) {
|
||||
bytesRead = pread(fd, chunk, chunkSize, offset);
|
||||
if (bytesRead > 0) {
|
||||
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
|
||||
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
|
||||
offset += bytesRead;
|
||||
} else if (bytesRead == -1 && errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We turn off Read Ahead that we turned on
|
||||
fcntl(fd, F_RDAHEAD, 0);
|
||||
if (sha1) {
|
||||
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";
|
||||
*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]];
|
||||
}
|
||||
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";
|
||||
// We turn off Read Ahead that we turned on
|
||||
fcntl(fd, F_RDAHEAD, 0);
|
||||
if (sha1) {
|
||||
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";
|
||||
*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]];
|
||||
}
|
||||
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";
|
||||
|
||||
*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]];
|
||||
*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]];
|
||||
}
|
||||
} @finally {
|
||||
free(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,22 +229,26 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return [self.machHeaders allKeys];
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
- (uint32_t)machFileType {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_EXECUTE) return YES;
|
||||
return NO;
|
||||
if (mach_header) return mach_header->filetype;
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
return [self machFileType] == MH_EXECUTE;
|
||||
}
|
||||
|
||||
- (BOOL)isDylib {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_DYLIB) return YES;
|
||||
return NO;
|
||||
return [self machFileType] == MH_DYLIB;
|
||||
}
|
||||
|
||||
- (BOOL)isBundle {
|
||||
return [self machFileType] == MH_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isKext {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) return YES;
|
||||
return NO;
|
||||
return [self machFileType] == MH_KEXT_BUNDLE;
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
@@ -233,17 +270,30 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
- (BOOL)isDMG {
|
||||
if (self.fileSize < 512) return NO;
|
||||
NSUInteger last512 = self.fileSize - 512;
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
|
||||
return (magic && memcmp("koly", magic, 4) == 0);
|
||||
}
|
||||
|
||||
- (NSString *)humanReadableFileType {
|
||||
if ([self isExecutable]) return @"Executable";
|
||||
if ([self isDylib]) return @"Dynamic Library";
|
||||
if ([self isBundle]) return @"Bundle/Plugin";
|
||||
if ([self isKext]) return @"Kernel Extension";
|
||||
if ([self isScript]) return @"Script";
|
||||
if ([self isXARArchive]) return @"XAR Archive";
|
||||
if ([self isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
}
|
||||
|
||||
#pragma mark Page Zero
|
||||
|
||||
- (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]];
|
||||
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];
|
||||
@@ -271,39 +321,62 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
#pragma mark Bundle Information
|
||||
|
||||
///
|
||||
/// Directories with a "Contents/Info.plist" entry can be mistaken as a bundle. To be considered an
|
||||
/// ancestor, the bundle must have a valid extension.
|
||||
///
|
||||
- (NSSet *)allowedAncestorExtensions {
|
||||
static NSSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSSet setWithArray:@[
|
||||
@"app",
|
||||
@"bundle",
|
||||
@"framework",
|
||||
@"kext",
|
||||
@"xctest",
|
||||
@"xpc",
|
||||
]];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
///
|
||||
/// Try and determine the bundle that the represented executable is contained within, if any.
|
||||
///
|
||||
/// Rationale: An NSBundle has a method executablePath for discovering the main binary within a
|
||||
/// bundle but provides no way to get an NSBundle object when only the executablePath is known.
|
||||
/// Also a bundle can contain multiple binaries within the MacOS folder and we want any of these
|
||||
/// Also a bundle can contain multiple binaries within its subdirectories and we want any of these
|
||||
/// to count as being part of the bundle.
|
||||
///
|
||||
/// This method relies on executable bundles being laid out as follows:
|
||||
/// This method walks up the path until a bundle is found, if any.
|
||||
///
|
||||
/// @code
|
||||
/// Bundle.app/
|
||||
/// Contents/
|
||||
/// MacOS/
|
||||
/// executable
|
||||
/// @endcode
|
||||
///
|
||||
/// If @c self.path is the full path to @c executable above, this method would return an
|
||||
/// NSBundle reference for Bundle.app.
|
||||
/// @param ancestor YES this will return the highest NSBundle, with a valid extension, found in the
|
||||
/// tree. NO will return the the lowest NSBundle, without validating the extension.
|
||||
///
|
||||
- (NSBundle *)findBundleWithAncestor:(BOOL)ancestor {
|
||||
NSBundle *bundle;
|
||||
NSMutableArray *pathComponents = [[self.path pathComponents] mutableCopy];
|
||||
|
||||
// Ignore the root path "/", for some reason this is considered a bundle.
|
||||
while (pathComponents.count > 1) {
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
if ([bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
|
||||
if (!ancestor ||
|
||||
[[self allowedAncestorExtensions] containsObject:bndl.bundlePath.pathExtension]) {
|
||||
bundle = bndl;
|
||||
}
|
||||
if (!ancestor) break;
|
||||
}
|
||||
[pathComponents removeLastObject];
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
- (NSBundle *)bundle {
|
||||
if (!self.bundleRef) {
|
||||
self.bundleRef = (NSBundle *)[NSNull null];
|
||||
|
||||
// Check that the full path is at least 4-levels deep:
|
||||
// e.g: /Calendar.app/Contents/MacOS/Calendar
|
||||
NSArray *pathComponents = [self.path pathComponents];
|
||||
NSUInteger pathComponentsCount = pathComponents.count;
|
||||
if (pathComponentsCount < 4) return nil;
|
||||
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponentsCount - 3)];
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = bndl;
|
||||
self.bundleRef =
|
||||
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
|
||||
}
|
||||
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
|
||||
}
|
||||
@@ -312,6 +385,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return [self.bundle bundlePath];
|
||||
}
|
||||
|
||||
- (void)setUseAncestorBundle:(BOOL)useAncestorBundle {
|
||||
if (self.useAncestorBundle != useAncestorBundle) {
|
||||
self.bundleRef = nil;
|
||||
self.infoDict = nil;
|
||||
}
|
||||
_useAncestorBundle = useAncestorBundle;
|
||||
}
|
||||
|
||||
- (NSDictionary *)infoPlist {
|
||||
if (!self.infoDict) {
|
||||
NSDictionary *d = [self embeddedPlist];
|
||||
@@ -391,13 +472,13 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
if (machHeader) {
|
||||
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
|
||||
machHeaders[[self nameForCPUType:mh->cputype]] = mhwo;
|
||||
machHeaders[[self nameForCPUType:mh->cputype cpuSubType:mh->cpusubtype]] = mhwo;
|
||||
} else {
|
||||
NSRange range = NSMakeRange(0, sizeof(struct fat_header));
|
||||
NSData *fatHeader = [self safeSubdataWithRange:range];
|
||||
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
|
||||
|
||||
if (fatHeader && (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM)) {
|
||||
if (fatHeader && (fh->magic == FAT_CIGAM || fh->magic == FAT_MAGIC)) {
|
||||
int nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
|
||||
range = NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch) * nfat_arch);
|
||||
NSMutableData *fatArchs = [[self safeSubdataWithRange:range] mutableCopy];
|
||||
@@ -407,11 +488,12 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
int offset = OSSwapBigToHostInt32(fat_arch[i].offset);
|
||||
int size = OSSwapBigToHostInt32(fat_arch[i].size);
|
||||
int cputype = OSSwapBigToHostInt(fat_arch[i].cputype);
|
||||
int cpusubtype = OSSwapBigToHostInt(fat_arch[i].cpusubtype);
|
||||
|
||||
range = NSMakeRange(offset, size);
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:range]];
|
||||
if (machHeader) {
|
||||
NSString *key = [self nameForCPUType:cputype];
|
||||
NSString *key = [self nameForCPUType:cputype cpuSubType:cpusubtype];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader
|
||||
offset:offset];
|
||||
machHeaders[key] = mhwo;
|
||||
@@ -469,24 +551,51 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
for (uint32_t i = 0; i < ncmds; ++i) {
|
||||
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
|
||||
if (!cmdData) return nil;
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
|
||||
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
|
||||
if (is64) {
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT_64 && memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
} else {
|
||||
struct segment_command *lc = (struct segment_command *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT && memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
|
||||
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
|
||||
for (uint32_t i = 0; i < nsects; ++i) {
|
||||
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
|
||||
if (!sectData) return nil;
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
|
||||
uint64_t sectoffset, sectsize = 0;
|
||||
BOOL found = NO;
|
||||
if (is64) {
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
sectoffset = sect->offset;
|
||||
sectsize = sect->size;
|
||||
found = YES;
|
||||
}
|
||||
} else {
|
||||
struct section *sect = (struct section *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
sectoffset = sect->offset;
|
||||
sectsize = sect->size;
|
||||
found = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(mhwo.offset + sectoffset,
|
||||
sectsize)];
|
||||
if (!plistData) return nil;
|
||||
NSDictionary *plist;
|
||||
plist = [NSPropertyListSerialization propertyListWithData:plistData
|
||||
@@ -518,8 +627,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
NSData *d = [self.fileHandle readDataOfLength:range.length];
|
||||
if (d.length != range.length) return nil;
|
||||
return d;
|
||||
}
|
||||
@catch (NSException *e) {
|
||||
} @catch (NSException *e) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
@@ -530,7 +638,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
/// is not the one who downloaded the file.
|
||||
///
|
||||
- (NSDictionary *)quarantineData {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir) {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir && NSURLQuarantinePropertiesKey) {
|
||||
self.quarantineDict = (NSDictionary *)[NSNull null];
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path];
|
||||
@@ -596,20 +704,15 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
///
|
||||
/// Return a human-readable string for a cpu_type_t.
|
||||
///
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
|
||||
switch (cpuType) {
|
||||
case CPU_TYPE_X86:
|
||||
return @"i386";
|
||||
case CPU_TYPE_X86_64:
|
||||
return @"x86-64";
|
||||
case CPU_TYPE_POWERPC:
|
||||
return @"ppc";
|
||||
case CPU_TYPE_POWERPC64:
|
||||
return @"ppc64";
|
||||
default:
|
||||
return @"unknown";
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType cpuSubType:(cpu_subtype_t)cpuSubType {
|
||||
const NXArchInfo *archInfo = NXGetArchInfoFromCpuType(cpuType, cpuSubType);
|
||||
NSString *arch;
|
||||
if (archInfo && archInfo->name) {
|
||||
arch = @(archInfo->name);
|
||||
} else {
|
||||
arch = [NSString stringWithFormat:@"%i:%i", cpuType, cpuSubType];
|
||||
}
|
||||
return nil;
|
||||
return arch;
|
||||
}
|
||||
|
||||
///
|
||||
@@ -634,7 +737,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
BOOL directory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
|
||||
return nil;
|
||||
} else if (directory) {
|
||||
} else if (directory && ![path isEqualToString:@"/"]) {
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:path];
|
||||
if (bundle) *bundle = bndl;
|
||||
return [bndl executablePath];
|
||||
@@ -643,4 +746,18 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Cache and return a MOLCodeSignChecker for the given file. If there was an error creating the
|
||||
/// code sign checker it will be returned in the passed-in error parameter.
|
||||
///
|
||||
- (MOLCodesignChecker *)codesignCheckerWithError:(NSError **)error {
|
||||
if (!self.cachedCodesignChecker && !self.codesignCheckerError) {
|
||||
NSError *e;
|
||||
self.cachedCodesignChecker = [[MOLCodesignChecker alloc] initWithBinaryPath:self.path error:&e];
|
||||
self.codesignCheckerError = e;
|
||||
}
|
||||
if (error) *error = self.codesignCheckerError;
|
||||
return self.cachedCodesignChecker;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
245
Source/common/SNTFileInfoTest.m
Normal file
245
Source/common/SNTFileInfoTest.m
Normal file
@@ -0,0 +1,245 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
@interface SNTFileInfoTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfoTest
|
||||
|
||||
- (NSString *)directoryBundle {
|
||||
NSString *rp = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
return [rp stringByAppendingPathComponent:@"testdata/DirectoryBundle"];
|
||||
}
|
||||
|
||||
- (NSString *)bundleExample {
|
||||
NSString *rp = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
return [rp stringByAppendingPathComponent:@"testdata/BundleExample.app"];
|
||||
}
|
||||
|
||||
- (void)testPathStandardizing {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/Applications/Safari.app"];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.path, @"/Applications/Safari.app/Contents/MacOS/Safari");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
|
||||
XCTAssertEqualObjects(sut.path, @"/bin/ls");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/DirectoryService"];
|
||||
XCTAssertEqualObjects(sut.path, @"/usr/libexec/dspluginhelperd");
|
||||
}
|
||||
|
||||
- (void)testSHA1 {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA1);
|
||||
XCTAssertEqual(sut.SHA1.length, 40);
|
||||
XCTAssertEqualObjects(sut.SHA1, @"3a865bf47b4ceba20496e0e66e39e4cfa101ffe6");
|
||||
}
|
||||
|
||||
- (void)testSHA256 {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA256);
|
||||
XCTAssertEqual(sut.SHA256.length, 64);
|
||||
XCTAssertEqualObjects(sut.SHA256,
|
||||
@"5e089b65a1e7a4696d84a34510710b6993d1de21250c41daaec63d9981083eba");
|
||||
}
|
||||
|
||||
- (void)testExecutable {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/sbin/launchd"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isExecutable);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testPageZero {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
path = [[NSBundle bundleForClass:[self class]] pathForResource:@"bad_pagezero" ofType:@""];
|
||||
sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/bless"];
|
||||
XCTAssertFalse(sut.isMissingPageZero);
|
||||
}
|
||||
|
||||
- (void)testKext {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:
|
||||
@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isKext);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testDylibs {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/system/libsystem_platform.dylib"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isDylib);
|
||||
XCTAssertTrue(sut.isFat);
|
||||
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testScript {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/h2ph"];
|
||||
|
||||
XCTAssertTrue(sut.isScript);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isMachO);
|
||||
}
|
||||
|
||||
- (void)testBundle {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testAncestorBundle {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
|
||||
NSString *ancestorBundlePath = path;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent];
|
||||
}
|
||||
XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath);
|
||||
}
|
||||
|
||||
- (void)testBundleIsAncestor {
|
||||
NSString *path = [NSBundle bundleForClass:[self class]].bundlePath;
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testDirectoryBundleIsNotAncestor {
|
||||
NSString *path = [self directoryBundle];
|
||||
NSString *directoryBundle = @"/tmp/DirectoryBundle";
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
[fm removeItemAtPath:directoryBundle error:NULL];
|
||||
[fm copyItemAtPath:path toPath:directoryBundle error:NULL];
|
||||
path = [directoryBundle stringByAppendingString:@"/Contents/Resources/BundleExample.app"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testBundleCacheReset {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
|
||||
NSString *ancestorBundlePath = path;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent];
|
||||
}
|
||||
XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath);
|
||||
}
|
||||
|
||||
- (void)testNonBundle {
|
||||
SNTFileInfo *sut =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
}
|
||||
|
||||
- (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..
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,33 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Simple file watching class using dispatch sources. Will automatically
|
||||
/// reload the watch if the file is deleted and continue watching for
|
||||
/// events until deallocated.
|
||||
///
|
||||
@interface SNTFileWatcher : NSObject
|
||||
|
||||
///
|
||||
/// Designated initializer
|
||||
/// Initializes the watcher and begins watching for modifications.
|
||||
///
|
||||
/// @param filePath the file to watch.
|
||||
/// @param handler the handler to call when changes happen. The argument to the block is the
|
||||
/// type of change that happened as a bitmask to be compared with DISPATCH_VNODE_* constants.
|
||||
///
|
||||
- (nonnull instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
handler:(nonnull void (^)(unsigned long))handler;
|
||||
|
||||
@end
|
||||
@@ -1,101 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTFileWatcher.h"
|
||||
|
||||
#import "SNTStrengthify.h"
|
||||
|
||||
@interface SNTFileWatcher ()
|
||||
@property NSString *filePath;
|
||||
@property(strong) void (^handler)(unsigned long);
|
||||
|
||||
@property dispatch_source_t source;
|
||||
@end
|
||||
|
||||
@implementation SNTFileWatcher
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
handler:(nonnull void (^)(unsigned long))handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_handler = handler;
|
||||
[self startWatchingFile];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopWatchingFile];
|
||||
}
|
||||
|
||||
- (void)startWatchingFile {
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
||||
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME |
|
||||
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB);
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
int fd = -1;
|
||||
while ((fd = open([self.filePath fileSystemRepresentation], O_EVTONLY | O_CLOEXEC)) < 0) {
|
||||
usleep(200000); // wait 200ms
|
||||
}
|
||||
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
|
||||
|
||||
WEAKIFY(self);
|
||||
|
||||
dispatch_source_set_event_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
unsigned long data = dispatch_source_get_data(self.source);
|
||||
self.handler(data);
|
||||
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
|
||||
[self stopWatchingFile];
|
||||
[self startWatchingFile];
|
||||
}
|
||||
sleep(2);
|
||||
});
|
||||
|
||||
dispatch_source_set_registration_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
self.handler(0);
|
||||
});
|
||||
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
if (fd > 0) close(fd);
|
||||
});
|
||||
|
||||
dispatch_resume(self.source);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopWatchingFile {
|
||||
if (!self.source) return;
|
||||
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
dispatch_source_set_event_handler_f(self.source, NULL);
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
close(fd);
|
||||
});
|
||||
|
||||
dispatch_source_cancel(self.source);
|
||||
self.source = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -16,26 +16,34 @@
|
||||
/// Common defines between kernel <-> userspace
|
||||
///
|
||||
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifndef SANTA__COMMON__KERNELCOMMON_H
|
||||
#define SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
// Defines the lengths of paths and Vnode IDs passed around.
|
||||
#define MAX_VNODE_ID_STR 21 // digits in UINT64_MAX + 1 for NULL-terminator
|
||||
#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 unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
kSantaUserClientAllowBinary,
|
||||
kSantaUserClientAllowCompiler,
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientAcknowledgeBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientRemoveCacheEntry,
|
||||
kSantaUserClientCacheCount,
|
||||
kSantaUserClientCheckCache,
|
||||
kSantaUserClientCacheBucketCount,
|
||||
kSantaUserClientFilemodPrefixFilterAdd,
|
||||
kSantaUserClientFilemodPrefixFilterReset,
|
||||
|
||||
// Any methods supported by the driver should be added above this line to
|
||||
// ensure this remains the count of methods.
|
||||
@@ -44,7 +52,7 @@ enum SantaDriverMethods {
|
||||
|
||||
typedef enum {
|
||||
QUEUETYPE_DECISION,
|
||||
QUEUETYPE_LOG
|
||||
QUEUETYPE_LOG,
|
||||
} santa_queuetype_t;
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
@@ -59,6 +67,12 @@ typedef enum {
|
||||
// RESPONSES
|
||||
ACTION_RESPOND_ALLOW = 20,
|
||||
ACTION_RESPOND_DENY = 21,
|
||||
ACTION_RESPOND_TOOLONG = 22,
|
||||
ACTION_RESPOND_ACK = 23,
|
||||
ACTION_RESPOND_ALLOW_COMPILER = 24,
|
||||
// The following response is stored only in the kernel decision cache.
|
||||
// It is removed by SNTCompilerController
|
||||
ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE = 25,
|
||||
|
||||
// NOTIFY
|
||||
ACTION_NOTIFY_EXEC = 30,
|
||||
@@ -67,21 +81,45 @@ typedef enum {
|
||||
ACTION_NOTIFY_LINK = 33,
|
||||
ACTION_NOTIFY_EXCHANGE = 34,
|
||||
ACTION_NOTIFY_DELETE = 35,
|
||||
ACTION_NOTIFY_WHITELIST = 36,
|
||||
ACTION_NOTIFY_FORK = 37,
|
||||
ACTION_NOTIFY_EXIT = 38,
|
||||
|
||||
// ERROR
|
||||
ACTION_ERROR = 99,
|
||||
} santa_action_t;
|
||||
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY)
|
||||
(x == ACTION_RESPOND_ALLOW || \
|
||||
x == ACTION_RESPOND_DENY || \
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER || \
|
||||
x == ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE)
|
||||
|
||||
// Struct to manage vnode IDs
|
||||
typedef struct santa_vnode_id_t {
|
||||
uint64_t fsid;
|
||||
uint64_t fileid;
|
||||
|
||||
#ifdef __cplusplus
|
||||
bool operator==(const santa_vnode_id_t& rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
// This _must not_ be used for anything security-sensitive. It exists solely to make
|
||||
// the msleep/wakeup calls easier.
|
||||
uint64_t unsafe_simple_id() const {
|
||||
return (((uint64_t)fsid << 32) | fileid);
|
||||
}
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
// Message struct that is sent down the IODataQueue.
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
uint64_t vnode_id;
|
||||
santa_vnode_id_t vnode_id;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
int pidversion;
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
@@ -90,6 +128,18 @@ typedef struct {
|
||||
// While process names can technically be 4*MAXPATHLEN, that never
|
||||
// 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.
|
||||
void *es_message;
|
||||
|
||||
// For messages that originate from EndpointSecurity, this points to an NSArray of the arguments.
|
||||
void *args_array;
|
||||
} santa_message_t;
|
||||
|
||||
// Used for the kSantaUserClientCacheBucketCount request.
|
||||
typedef struct {
|
||||
uint16_t per_bucket[1024];
|
||||
uint64_t start;
|
||||
} santa_bucket_count_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
@@ -24,16 +24,22 @@
|
||||
#include <IOKit/IOLib.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#else // DEBUG
|
||||
#define LOGD(...)
|
||||
#define LOGD(format, ...)
|
||||
#endif // DEBUG
|
||||
#define LOGI(...) IOLog("I santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGW(...) IOLog("W santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGE(...) IOLog("E santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
|
||||
|
||||
#else // KERNEL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARN,
|
||||
@@ -58,6 +64,10 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
#endif // SANTA__COMMON__LOGGING_H
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTLogging.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
@@ -24,25 +26,28 @@ static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
asl_close((aslclient)arg);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static const char *binaryName;
|
||||
static NSString *binaryName;
|
||||
static dispatch_once_t pred;
|
||||
static pthread_key_t syslogKey = 0;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
binaryName = [[[NSProcessInfo processInfo] processName] UTF8String];
|
||||
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;
|
||||
}
|
||||
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"]) {
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
|
||||
useSyslog = YES;
|
||||
pthread_key_create(&syslogKey, syslogClientDestructor);
|
||||
}
|
||||
@@ -58,8 +63,11 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
if (useSyslog) {
|
||||
aslclient client = (aslclient)pthread_getspecific(syslogKey);
|
||||
if (client == NULL) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
client = asl_open(NULL, "com.google.santa", 0);
|
||||
asl_set_filter(client, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
|
||||
#pragma clang diagnostic pop
|
||||
pthread_setspecific(syslogKey, client);
|
||||
}
|
||||
|
||||
@@ -76,7 +84,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
break;
|
||||
case LOG_LEVEL_INFO:
|
||||
levelName = "I";
|
||||
syslogLevel = ASL_LEVEL_INFO;
|
||||
syslogLevel = ASL_LEVEL_NOTICE; // Maps to ULS Default
|
||||
break;
|
||||
case LOG_LEVEL_DEBUG:
|
||||
levelName = "D";
|
||||
@@ -84,7 +92,10 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
break;
|
||||
}
|
||||
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName.UTF8String, s.UTF8String);
|
||||
#pragma clang diagnostic pop
|
||||
} else {
|
||||
[s appendString:@"\n"];
|
||||
size_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
259
Source/common/SNTPrefixTree.cc
Normal file
259
Source/common/SNTPrefixTree.cc
Normal file
@@ -0,0 +1,259 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
#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
|
||||
|
||||
#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_mtx_lock(l) l->lock()
|
||||
#define lck_mtx_unlock(l) l->unlock()
|
||||
#endif // KERNEL
|
||||
|
||||
SNTPrefixTree::SNTPrefixTree(uint32_t max_nodes) {
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
max_nodes_ = 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_attr_ = lck_attr_alloc_init();
|
||||
|
||||
spt_lock_ = lck_rw_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
spt_add_lock_ = lck_mtx_alloc_init(spt_lock_grp_, spt_lock_attr_);
|
||||
#else
|
||||
pthread_rwlock_init(&spt_lock_, nullptr);
|
||||
spt_add_lock_ = new std::mutex;
|
||||
#endif
|
||||
}
|
||||
|
||||
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.
|
||||
lck_mtx_lock(spt_add_lock_);
|
||||
|
||||
// Don't allow an empty prefix.
|
||||
if (prefix[0] == '\0') return kIOReturnBadArgument;
|
||||
|
||||
LOGD("Trying to add prefix: %s", prefix);
|
||||
|
||||
// Enforce max tree depth.
|
||||
size_t len = strnlen(prefix, max_nodes_);
|
||||
|
||||
// Grab a shared lock until a new branch is required.
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
for (int 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];
|
||||
|
||||
// Create the child if it does not exist.
|
||||
if (!node->children[value]) {
|
||||
// Upgrade the shared lock.
|
||||
// If the upgrade fails, the shared lock is released.
|
||||
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
|
||||
// Grab a new exclusive lock.
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
// Is there enough room for the rest of the prefix?
|
||||
if ((node_count_ + (len - i)) > max_nodes_) {
|
||||
LOGE("Prefix tree is full, can not add: %s", prefix);
|
||||
|
||||
if (node_count) *node_count = node_count_;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
lck_mtx_unlock(spt_add_lock_);
|
||||
return kIOReturnNoResources;
|
||||
}
|
||||
|
||||
// Create the rest of the prefix.
|
||||
while (i < len) {
|
||||
value = prefix[i++];
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
node->children[value] = new_node;
|
||||
++node_count_;
|
||||
|
||||
node = new_node;
|
||||
}
|
||||
|
||||
// This is the end, mark the node as a prefix.
|
||||
LOGD("Added prefix: %s", prefix);
|
||||
|
||||
node->isPrefix = true;
|
||||
|
||||
// Downgrade the exclusive lock
|
||||
lck_rw_lock_exclusive_to_shared(spt_lock_);
|
||||
} else if (i + 1 == len) {
|
||||
// If the child does exist and it is the end...
|
||||
// Set the new, higher prefix and prune the now dead nodes.
|
||||
|
||||
if (!lck_rw_lock_shared_to_exclusive(spt_lock_)) {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
PruneNode(node->children[value]);
|
||||
|
||||
SantaPrefixNode *new_node = new SantaPrefixNode();
|
||||
new_node->isPrefix = true;
|
||||
|
||||
node->children[value] = new_node;
|
||||
++node_count_;
|
||||
|
||||
LOGD("Added prefix: %s", prefix);
|
||||
|
||||
lck_rw_lock_exclusive_to_shared(spt_lock_);
|
||||
}
|
||||
|
||||
// Get ready for the next iteration.
|
||||
node = node->children[value];
|
||||
}
|
||||
|
||||
if (node_count) *node_count = node_count_;
|
||||
|
||||
lck_rw_unlock_shared(spt_lock_);
|
||||
lck_mtx_unlock(spt_add_lock_);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
bool SNTPrefixTree::HasPrefix(const char *string) {
|
||||
lck_rw_lock_shared(spt_lock_);
|
||||
|
||||
auto found = false;
|
||||
|
||||
SantaPrefixNode *node = root_;
|
||||
|
||||
// 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.
|
||||
node = node->children[(uint8_t)*p++];
|
||||
|
||||
// If it doesn't exist in the tree, no match.
|
||||
if (!node) break;
|
||||
|
||||
// If it does exist, is it a prefix?
|
||||
if (node->isPrefix) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lck_rw_unlock_shared(spt_lock_);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void SNTPrefixTree::Reset() {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
|
||||
PruneNode(root_);
|
||||
root_ = new SantaPrefixNode();
|
||||
node_count_ = 0;
|
||||
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
}
|
||||
|
||||
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.
|
||||
auto stack = new SantaPrefixNode *[node_count_ + 1];
|
||||
if (!stack) {
|
||||
LOGE("Unable to prune tree!");
|
||||
|
||||
return;
|
||||
}
|
||||
auto count = 0;
|
||||
|
||||
// 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.
|
||||
while (count) {
|
||||
auto node = stack[--count];
|
||||
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
if (!node->children[i]) continue;
|
||||
stack[count++] = node->children[i];
|
||||
}
|
||||
|
||||
delete node;
|
||||
--node_count_;
|
||||
}
|
||||
|
||||
delete[] stack;
|
||||
}
|
||||
|
||||
SNTPrefixTree::~SNTPrefixTree() {
|
||||
lck_rw_lock_exclusive(spt_lock_);
|
||||
PruneNode(root_);
|
||||
root_ = nullptr;
|
||||
lck_rw_unlock_exclusive(spt_lock_);
|
||||
|
||||
#ifdef KERNEL
|
||||
if (spt_lock_) {
|
||||
lck_rw_free(spt_lock_, spt_lock_grp_);
|
||||
spt_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_add_lock_) {
|
||||
lck_mtx_free(spt_add_lock_, spt_lock_grp_);
|
||||
spt_add_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_attr_) {
|
||||
lck_attr_free(spt_lock_attr_);
|
||||
spt_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_) {
|
||||
lck_grp_free(spt_lock_grp_);
|
||||
spt_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (spt_lock_grp_attr_) {
|
||||
lck_grp_attr_free(spt_lock_grp_attr_);
|
||||
spt_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
#else
|
||||
pthread_rwlock_destroy(&spt_lock_);
|
||||
#endif
|
||||
}
|
||||
100
Source/common/SNTPrefixTree.h
Normal file
100
Source/common/SNTPrefixTree.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H
|
||||
#define SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H
|
||||
|
||||
#include <IOKit/IOReturn.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <libkern/locks.h>
|
||||
#else
|
||||
// Support for unit testing.
|
||||
#include <mutex>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#endif // KERNEL
|
||||
|
||||
///
|
||||
/// SantaPrefixTree is a simple prefix tree implementation.
|
||||
/// Operations are thread safe.
|
||||
///
|
||||
class SNTPrefixTree {
|
||||
public:
|
||||
// Add a prefix to the tree.
|
||||
// Optionally pass node_count to get the number of nodes after the add.
|
||||
IOReturn AddPrefix(const char *, uint64_t *node_count = nullptr);
|
||||
|
||||
// Check if the tree has a prefix for string.
|
||||
bool HasPrefix(const char *string);
|
||||
|
||||
// Reset the tree.
|
||||
void Reset();
|
||||
|
||||
SNTPrefixTree(uint32_t max_nodes = kDefaultMaxNodes);
|
||||
~SNTPrefixTree();
|
||||
|
||||
private:
|
||||
///
|
||||
/// SantaPrefixNode is a wrapper class that represents one byte.
|
||||
/// 1 node can represent a whole ASCII character.
|
||||
/// For example a pointer to the 'A' node will be stored at children[0x41].
|
||||
/// 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]
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// TODO(bur): Potentially convert this into a full on radix tree.
|
||||
///
|
||||
class SantaPrefixNode {
|
||||
public:
|
||||
bool isPrefix;
|
||||
SantaPrefixNode *children[256];
|
||||
};
|
||||
|
||||
// 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.
|
||||
void PruneNode(SantaPrefixNode *);
|
||||
|
||||
SantaPrefixNode *root_;
|
||||
|
||||
// Each node takes up ~2k, assuming MAXPATHLEN is 1024 max out at ~2MB.
|
||||
static const uint32_t kDefaultMaxNodes = MAXPATHLEN;
|
||||
uint32_t max_nodes_;
|
||||
uint32_t node_count_;
|
||||
|
||||
#ifdef KERNEL
|
||||
lck_grp_t *spt_lock_grp_;
|
||||
lck_grp_attr_t *spt_lock_grp_attr_;
|
||||
lck_attr_t *spt_lock_attr_;
|
||||
lck_rw_t *spt_lock_;
|
||||
lck_mtx_t *spt_add_lock_;
|
||||
#else // KERNEL
|
||||
pthread_rwlock_t spt_lock_;
|
||||
std::mutex *spt_add_lock_;
|
||||
#endif // KERNEL
|
||||
};
|
||||
|
||||
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */
|
||||
70
Source/common/SNTPrefixTreeTest.mm
Normal file
70
Source/common/SNTPrefixTreeTest.mm
Normal file
@@ -0,0 +1,70 @@
|
||||
/// Copyright 2018 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
@interface SNTPrefixTreeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTPrefixTreeTest
|
||||
|
||||
- (void)testAddAndHas {
|
||||
auto t = SNTPrefixTree();
|
||||
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
|
||||
t.AddPrefix("/private/var/tmp/");
|
||||
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
|
||||
}
|
||||
|
||||
- (void)testReset {
|
||||
auto t = SNTPrefixTree();
|
||||
t.AddPrefix("/private/var/tmp/");
|
||||
XCTAssertTrue(t.HasPrefix("/private/var/tmp/file1"));
|
||||
t.Reset();
|
||||
XCTAssertFalse(t.HasPrefix("/private/var/tmp/file1"));
|
||||
}
|
||||
|
||||
- (void)testThreading {
|
||||
uint32_t count = 4096;
|
||||
auto t = new SNTPrefixTree(count * (uint32_t)[NSUUID UUID].UUIDString.length);
|
||||
|
||||
NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
[UUIDs addObject:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
// Create a bunch of background noise.
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
dispatch_apply(UINT64_MAX, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
t->HasPrefix([UUIDs[i % count] UTF8String]);
|
||||
});
|
||||
});
|
||||
|
||||
// Fill up the tree.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
if (t->AddPrefix([UUIDs[i] UTF8String]) != kIOReturnSuccess) {
|
||||
XCTFail();
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure every leaf byte is found.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
if (!t->HasPrefix([UUIDs[i] UTF8String])) {
|
||||
XCTFail();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,7 +12,9 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Represents a Rule.
|
||||
@@ -39,12 +41,32 @@
|
||||
///
|
||||
@property(copy) NSString *customMsg;
|
||||
|
||||
///
|
||||
/// The time when this rule was last retrieved from the rules database, if rule is transitive.
|
||||
/// Stored as number of seconds since 00:00:00 UTC on 1 January 2001.
|
||||
///
|
||||
@property(readonly) NSUInteger timestamp;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
///
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp;
|
||||
|
||||
///
|
||||
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
|
||||
///
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg;
|
||||
|
||||
///
|
||||
/// Sets timestamp of rule to the current time.
|
||||
///
|
||||
- (void)resetTimestamp;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,26 +12,51 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTRule.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
@interface SNTRule()
|
||||
@property(readwrite) NSUInteger timestamp;
|
||||
@end
|
||||
|
||||
@implementation SNTRule
|
||||
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
customMsg:(NSString *)customMsg
|
||||
timestamp:(NSUInteger)timestamp {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_shasum = shasum;
|
||||
_state = state;
|
||||
_type = type;
|
||||
_customMsg = customMsg;
|
||||
_timestamp = timestamp;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
self = [self initWithShasum:shasum
|
||||
state:state
|
||||
type:type
|
||||
customMsg:customMsg
|
||||
timestamp:0];
|
||||
// Initialize timestamp to current time if rule is transitive.
|
||||
if (self && state == SNTRuleStateAllowTransitive) {
|
||||
[self resetTimestamp];
|
||||
}
|
||||
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 DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
|
||||
@@ -44,6 +69,7 @@
|
||||
ENCODE(@(self.state), @"state");
|
||||
ENCODE(@(self.type), @"type");
|
||||
ENCODE(self.customMsg, @"custommsg");
|
||||
ENCODE(@(self.timestamp), @"timestamp");
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
@@ -53,12 +79,14 @@
|
||||
_state = [DECODE(NSNumber, @"state") intValue];
|
||||
_type = [DECODE(NSNumber, @"type") intValue];
|
||||
_customMsg = DECODE(NSString, @"custommsg");
|
||||
_timestamp = [DECODE(NSNumber, @"timestamp") unsignedIntegerValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#undef DECODE
|
||||
#undef ENCODE
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (other == self) return YES;
|
||||
@@ -77,8 +105,14 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
|
||||
self.shasum, self.state, self.type];
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
|
||||
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
|
||||
}
|
||||
|
||||
# pragma mark Last-access Timestamp
|
||||
|
||||
- (void)resetTimestamp {
|
||||
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
@@ -20,7 +22,7 @@
|
||||
@interface SNTStoredEvent : NSObject<NSSecureCoding>
|
||||
|
||||
///
|
||||
/// An index for this event, empty unless the event came from the database.
|
||||
/// An index for this event, randomly generated during initialization.
|
||||
///
|
||||
@property NSNumber *idx;
|
||||
|
||||
@@ -34,6 +36,28 @@
|
||||
///
|
||||
@property NSString *filePath;
|
||||
|
||||
///
|
||||
/// Set to YES if the event is a part of a bundle. When an event is passed to SantaGUI this propery
|
||||
/// will be used as an indicator to to kick off bundle hashing as necessary. Default value is NO.
|
||||
///
|
||||
@property BOOL needsBundleHash;
|
||||
|
||||
///
|
||||
/// If the executed file was part of a bundle, this is the calculated hash of all the nested
|
||||
/// executables within the bundle.
|
||||
///
|
||||
@property NSString *fileBundleHash;
|
||||
|
||||
///
|
||||
/// If the executed file was part of a bundle, this is the time in ms it took to hash the bundle.
|
||||
///
|
||||
@property NSNumber *fileBundleHashMilliseconds;
|
||||
|
||||
///
|
||||
/// If the executed file was part of a bundle, this is the total count of related mach-o binaries.
|
||||
///
|
||||
@property NSNumber *fileBundleBinaryCount;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the CFBundleDisplayName, if it exists
|
||||
/// or the CFBundleName if not.
|
||||
@@ -45,6 +69,11 @@
|
||||
///
|
||||
@property NSString *fileBundlePath;
|
||||
|
||||
///
|
||||
/// The relative path to the bundle's main executable.
|
||||
///
|
||||
@property NSString *fileBundleExecutableRelPath;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the CFBundleID.
|
||||
///
|
||||
|
||||
@@ -12,12 +12,15 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
@implementation SNTStoredEvent
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
|
||||
#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) \
|
||||
@@ -33,8 +36,13 @@
|
||||
ENCODE(self.fileSHA256, @"fileSHA256");
|
||||
ENCODE(self.filePath, @"filePath");
|
||||
|
||||
ENCODE(@(self.needsBundleHash), @"needsBundleHash");
|
||||
ENCODE(self.fileBundleHash, @"fileBundleHash");
|
||||
ENCODE(self.fileBundleHashMilliseconds, @"fileBundleHashMilliseconds");
|
||||
ENCODE(self.fileBundleBinaryCount, @"fileBundleBinaryCount");
|
||||
ENCODE(self.fileBundleName, @"fileBundleName");
|
||||
ENCODE(self.fileBundlePath, @"fileBundlePath");
|
||||
ENCODE(self.fileBundleExecutableRelPath, @"fileBundleExecutableRelPath");
|
||||
ENCODE(self.fileBundleID, @"fileBundleID");
|
||||
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
|
||||
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
|
||||
@@ -57,6 +65,14 @@
|
||||
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_idx = @(arc4random());
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -64,8 +80,13 @@
|
||||
_fileSHA256 = DECODE(NSString, @"fileSHA256");
|
||||
_filePath = DECODE(NSString, @"filePath");
|
||||
|
||||
_needsBundleHash = [DECODE(NSNumber, @"needsBundleHash") boolValue];
|
||||
_fileBundleHash = DECODE(NSString, @"fileBundleHash");
|
||||
_fileBundleHashMilliseconds = DECODE(NSNumber, @"fileBundleHashMilliseconds");
|
||||
_fileBundleBinaryCount = DECODE(NSNumber, @"fileBundleBinaryCount");
|
||||
_fileBundleName = DECODE(NSString, @"fileBundleName");
|
||||
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
|
||||
_fileBundleExecutableRelPath = DECODE(NSString, @"fileBundleExecutableRelPath");
|
||||
_fileBundleID = DECODE(NSString, @"fileBundleID");
|
||||
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
|
||||
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
|
||||
@@ -111,4 +132,6 @@
|
||||
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// Simple class for fetching system information
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTSystemInfo.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@implementation SNTSystemInfo
|
||||
|
||||
|
||||
70
Source/common/SNTXPCBundleServiceInterface.h
Normal file
70
Source/common/SNTXPCBundleServiceInterface.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/// Copyright 2017 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
|
||||
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
|
||||
|
||||
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
|
||||
@protocol SNTBundleServiceXPC
|
||||
|
||||
///
|
||||
/// @param listener The listener to connect back to the SantaGUI.
|
||||
///
|
||||
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
|
||||
|
||||
///
|
||||
/// Hash a bundle for an event. The SNTBundleHashBlock will be called with nil parameters if a
|
||||
/// failure or cancellation occurs.
|
||||
///
|
||||
/// @param event The event that includes the fileBundlePath to be hashed. This method will
|
||||
/// attempt to to find and use the ancestor bundle as a starting point.
|
||||
/// @param reply A SNTBundleHashBlock to be executed upon completion or cancellation.
|
||||
///
|
||||
/// @note If there is a current NSProgress when called this method will report back its progress.
|
||||
///
|
||||
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
|
||||
|
||||
///
|
||||
/// santabundleservice is launched on demand by launchd, call spindown to let santabundleservice know you are done with it.
|
||||
///
|
||||
- (void)spindown;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTXPCBundleServiceInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTBundleServiceXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
|
||||
///
|
||||
+ (NSXPCInterface *)bundleServiceInterface;
|
||||
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with santabundleservice.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
43
Source/common/SNTXPCBundleServiceInterface.m
Normal file
43
Source/common/SNTXPCBundleServiceInterface.m
Normal file
@@ -0,0 +1,43 @@
|
||||
/// Copyright 2017 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCBundleServiceInterface
|
||||
|
||||
+ (NSXPCInterface *)bundleServiceInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(hashBundleBinariesForEvent:reply:)
|
||||
argumentIndex:1
|
||||
ofReply:YES];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
return @"com.google.santa.bundleservice";
|
||||
}
|
||||
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:YES];
|
||||
c.remoteInterface = [self bundleServiceInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,126 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
/**
|
||||
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
|
||||
validation of connecting clients and forced connection establishment.
|
||||
|
||||
Example server started by @c launchd where the @c launchd job has a @c MachServices key:
|
||||
|
||||
@code
|
||||
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
|
||||
conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
conn.exportedObject = myObject;
|
||||
[conn resume];
|
||||
@endcode
|
||||
|
||||
Example client, connecting to above server:
|
||||
|
||||
@code
|
||||
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer"
|
||||
withOptions:0];
|
||||
conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
|
||||
[conn resume];
|
||||
@endcode
|
||||
|
||||
The client can send a message to the server with:
|
||||
|
||||
@code
|
||||
[conn.remoteObjectProxy selectorInRemoteInterface];
|
||||
@endcode
|
||||
|
||||
One advantage of the way that SNTXPCConnection works over using NSXPCConnection directly is that
|
||||
from the client-side once the resume method has finished, the connection is either valid or the
|
||||
invalidation handler will be called. Ordinarily, the connection doesn't actually get made until
|
||||
the first message is sent across it.
|
||||
|
||||
@note messages are always delivered on a background thread!
|
||||
*/
|
||||
@interface SNTXPCConnection : NSObject<NSXPCListenerDelegate>
|
||||
|
||||
/**
|
||||
Initialize a new server with a given listener, provided by `[NSXPCListener anonymousListener]`.
|
||||
*/
|
||||
- (nullable instancetype)initServerWithListener:(nonnull NSXPCListener *)listener;
|
||||
|
||||
/**
|
||||
Initializer for the 'server' side of the connection, started by launchd.
|
||||
|
||||
@param name MachService name, must match the MachServices key in the launchd.plist
|
||||
*/
|
||||
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
|
||||
|
||||
/**
|
||||
Initializer a new client to a service exported by a LaunchDaemon.
|
||||
|
||||
@param name MachService name
|
||||
@param privileged Use YES if the server is running as root.
|
||||
*/
|
||||
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
|
||||
|
||||
/**
|
||||
Initialize a new client with a listener endpoint sent from another process.
|
||||
|
||||
@param listener An NSXPCListenerEndpoint to connect to.
|
||||
*/
|
||||
- (nullable instancetype)initClientWithListener:(nonnull NSXPCListenerEndpoint *)listener;
|
||||
|
||||
/**
|
||||
Call when the properties of the object have been set-up and you're ready for connections.
|
||||
|
||||
For clients, this call can take up to 2s to complete for connection to finish establishing though
|
||||
in basically all cases it will actually complete in a few milliseconds.
|
||||
*/
|
||||
- (void)resume;
|
||||
|
||||
/**
|
||||
Invalidate the connection(s). This must be done before the object can be released.
|
||||
*/
|
||||
- (void)invalidate;
|
||||
|
||||
/**
|
||||
The interface the remote object should conform to. (client)
|
||||
*/
|
||||
@property(retain, nullable) NSXPCInterface *remoteInterface;
|
||||
|
||||
/**
|
||||
A proxy to the object at the other end of the connection. (client)
|
||||
|
||||
@note If the connection to the server failed, this will be nil, so you can safely send messages
|
||||
and rely on the invalidationHandler for handling the failure.
|
||||
*/
|
||||
@property(readonly, nonatomic, nullable) id remoteObjectProxy;
|
||||
|
||||
/**
|
||||
The interface this object exports. (server)
|
||||
*/
|
||||
@property(retain, nullable) NSXPCInterface *exportedInterface;
|
||||
|
||||
/**
|
||||
The object that responds to messages from the other end. (server)
|
||||
*/
|
||||
@property(retain, nullable) id exportedObject;
|
||||
|
||||
/**
|
||||
A block to run when a/the connection is accepted and fully established.
|
||||
*/
|
||||
@property(copy, nullable) void (^acceptedHandler)(void);
|
||||
|
||||
/**
|
||||
A block to run when a/the connection is invalidated/interrupted/rejected.
|
||||
*/
|
||||
@property(copy, nullable) void (^invalidationHandler)(void);
|
||||
|
||||
@end
|
||||
@@ -1,191 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
#import "MOLCodesignChecker.h"
|
||||
|
||||
#import "SNTStrengthify.h"
|
||||
|
||||
/**
|
||||
Protocol used during connection establishment, @see SNTXPCConnectionInterface
|
||||
*/
|
||||
@protocol SNTXPCConnectionProtocol
|
||||
- (void)connectWithReply:(void (^)())reply;
|
||||
@end
|
||||
|
||||
/**
|
||||
Recipient object used during connection establishment. Each incoming connection
|
||||
has one of these objects created which accept the message in the protocol
|
||||
and call the block provided during creation before replying.
|
||||
|
||||
This allows the server to reset the connection's exported interface and
|
||||
object to the correct values after the client has sent the establishment message.
|
||||
*/
|
||||
@interface SNTXPCConnectionInterface : NSObject<SNTXPCConnectionProtocol>
|
||||
@property(strong) void (^block)(void);
|
||||
@end
|
||||
|
||||
@implementation SNTXPCConnectionInterface
|
||||
- (void)connectWithReply:(void (^)())reply {
|
||||
if (self.block) self.block();
|
||||
reply();
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SNTXPCConnection ()
|
||||
@property NSXPCInterface *validationInterface;
|
||||
|
||||
/// The XPC listener (server only).
|
||||
@property NSXPCListener *listenerObject;
|
||||
|
||||
/// The current connection object (client only).
|
||||
@property NSXPCConnection *currentConnection;
|
||||
@end
|
||||
|
||||
@implementation SNTXPCConnection
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
- (instancetype)initServerWithListener:(NSXPCListener *)listener {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_listenerObject = listener;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initServerWithName:(NSString *)name {
|
||||
return [self initServerWithListener:[[NSXPCListener alloc] initWithMachServiceName:name]];
|
||||
}
|
||||
|
||||
- (instancetype)initClientWithListener:(NSXPCListenerEndpoint *)listener {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:listener];
|
||||
if (!_currentConnection) return nil;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initClientWithName:(NSString *)name privileged:(BOOL)privileged {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSXPCConnectionOptions options = (privileged ? NSXPCConnectionPrivileged : 0);
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name options:options];
|
||||
if (!_currentConnection) return nil;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Connection set-up
|
||||
|
||||
- (void)resume {
|
||||
if (self.listenerObject) {
|
||||
self.listenerObject.delegate = self;
|
||||
[self.listenerObject resume];
|
||||
} else {
|
||||
WEAKIFY(self);
|
||||
|
||||
// Set-up the connection with the remote interface set to the validation interface,
|
||||
// send a message to the listener to finish establishing the connection
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
self.currentConnection.remoteObjectInterface = self.validationInterface;
|
||||
self.currentConnection.interruptionHandler = self.invalidationHandler;
|
||||
self.currentConnection.invalidationHandler = self.invalidationHandler;
|
||||
[self.currentConnection resume];
|
||||
[[self.currentConnection remoteObjectProxy] connectWithReply:^{
|
||||
STRONGIFY(self);
|
||||
// The connection is now established
|
||||
[self.currentConnection suspend];
|
||||
self.currentConnection.remoteObjectInterface = self.remoteInterface;
|
||||
[self.currentConnection resume];
|
||||
dispatch_semaphore_signal(sema);
|
||||
if (self.acceptedHandler) self.acceptedHandler();
|
||||
}];
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
|
||||
// Connection was not established in a reasonable time, invalidate.
|
||||
self.currentConnection.remoteObjectInterface = nil; // ensure clients don't try to use it.
|
||||
[self.currentConnection invalidate];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection {
|
||||
pid_t pid = connection.processIdentifier;
|
||||
MOLCodesignChecker *otherCS = [[MOLCodesignChecker alloc] initWithPID:pid];
|
||||
if (![otherCS signingInformationMatches:[[MOLCodesignChecker alloc] initWithSelf]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// The client passed the code signature check, now we need to resume the listener and
|
||||
// return YES so that the client can send the connectWithReply message. Once the client does
|
||||
// we reset the connection's exportedInterface and exportedObject.
|
||||
SNTXPCConnectionInterface *ci = [[SNTXPCConnectionInterface alloc] init];
|
||||
WEAKIFY(self);
|
||||
WEAKIFY(connection);
|
||||
ci.block = ^{
|
||||
STRONGIFY(self)
|
||||
STRONGIFY(connection);
|
||||
[connection suspend];
|
||||
connection.invalidationHandler = connection.interruptionHandler = ^{
|
||||
if (self.invalidationHandler) self.invalidationHandler();
|
||||
};
|
||||
connection.exportedInterface = self.exportedInterface;
|
||||
connection.exportedObject = self.exportedObject;
|
||||
[connection resume];
|
||||
|
||||
// The connection is now established.
|
||||
if (self.acceptedHandler) self.acceptedHandler();
|
||||
};
|
||||
connection.exportedInterface = self.validationInterface;
|
||||
connection.exportedObject = ci;
|
||||
[connection resume];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)remoteObjectProxy {
|
||||
if (self.currentConnection.remoteObjectInterface &&
|
||||
self.currentConnection.remoteObjectInterface != self.validationInterface) {
|
||||
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
|
||||
[self.currentConnection invalidate];
|
||||
}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Connection tear-down
|
||||
|
||||
- (void)invalidate {
|
||||
if (self.currentConnection) {
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
} else if (self.listenerObject) {
|
||||
[self.listenerObject invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,58 +12,48 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@class SNTXPCConnection;
|
||||
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
|
||||
|
||||
///
|
||||
/// Protocol implemented by santad and utilized by santactl
|
||||
/// Protocol implemented by santad and utilized by santactl (privileged operations)
|
||||
///
|
||||
@protocol SNTDaemonControlXPC
|
||||
@protocol SNTDaemonControlXPC <SNTUnprivilegedDaemonControlXPC>
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
///
|
||||
- (void)cacheCount:(void (^)(int64_t))reply;
|
||||
- (void)flushCache:(void (^)(BOOL))reply;
|
||||
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(NSError *error))reply;
|
||||
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
|
||||
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
///
|
||||
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply;
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply;
|
||||
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
|
||||
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
|
||||
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
|
||||
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
|
||||
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)(void))reply;
|
||||
- (void)setXsrfToken:(NSString *)token reply:(void (^)(void))reply;
|
||||
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
|
||||
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)(void))reply;
|
||||
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
|
||||
- (void)setAllowedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
|
||||
- (void)setBlockedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
|
||||
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
|
||||
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;
|
||||
|
||||
///
|
||||
/// GUI Ops
|
||||
/// Syncd Ops
|
||||
///
|
||||
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
|
||||
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
|
||||
|
||||
@end
|
||||
|
||||
@@ -72,18 +62,23 @@
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceId;
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTDaemonControlXPC protocol.
|
||||
/// Returns the SystemExtension ID for this service.
|
||||
///
|
||||
+ (NSString *)systemExtensionID;
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTUnprivilegedDaemonControlXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)controlInterface;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured SNTXPCConnection for communicating with santad.
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with santad.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (SNTXPCConnection *)configuredConnection;
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,21 +12,35 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTXPCControlInterface.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
|
||||
#import "SNTRule.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
NSString *const kBundleID = @"com.google.santa.daemon";
|
||||
|
||||
@implementation SNTXPCControlInterface
|
||||
|
||||
+ (NSString *)serviceId {
|
||||
return @"SantaXPCControl";
|
||||
+ (NSString *)serviceID {
|
||||
if ([[SNTConfigurator configurator] enableSystemExtension]) {
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
// "teamid.com.google.santa.daemon.xpc"
|
||||
NSString *t = cs.signingInformation[@"teamid"];
|
||||
return [NSString stringWithFormat:@"%@.%@.xpc", t, kBundleID];
|
||||
}
|
||||
return kBundleID;
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
|
||||
+ (NSString *)systemExtensionID {
|
||||
return kBundleID;
|
||||
}
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(databaseEventsPending:)
|
||||
argumentIndex:0
|
||||
@@ -36,12 +50,17 @@
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
|
||||
[self initializeControlInterface:r];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (SNTXPCConnection *)configuredConnection {
|
||||
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithName:[self serviceId]
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:YES];
|
||||
c.remoteInterface = [self controlInterface];
|
||||
return c;
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@@ -20,6 +23,11 @@
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
binaryCount:(uint64_t)binaryCount
|
||||
fileCount:(uint64_t)fileCount
|
||||
hashedCount:(uint64_t)hashedCount;
|
||||
@end
|
||||
|
||||
@interface SNTXPCNotifierInterface : NSObject
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTXPCNotifierInterface.h"
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
|
||||
@implementation SNTXPCNotifierInterface
|
||||
|
||||
|
||||
56
Source/common/SNTXPCSyncServiceInterface.h
Normal file
56
Source/common/SNTXPCSyncServiceInterface.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/// Copyright 2020 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>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// A block that reports the number of rules processed.
|
||||
/// 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 SNTSyncServiceXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
- (void)performFullSyncWithReply:(SNTFullSyncReplyBlock)reply;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncServiceInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncServiceXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
|
||||
///
|
||||
+ (NSXPCInterface *)syncServiceInterface;
|
||||
|
||||
///
|
||||
/// Returns the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceID;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured MOLXPCConnection for communicating with syncservice.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (MOLXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
43
Source/common/SNTXPCSyncServiceInterface.m
Normal file
43
Source/common/SNTXPCSyncServiceInterface.m
Normal file
@@ -0,0 +1,43 @@
|
||||
/// Copyright 2020 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/SNTXPCSyncServiceInterface.h"
|
||||
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCSyncServiceInterface
|
||||
|
||||
+ (NSXPCInterface *)syncServiceInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceXPC)];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:fromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (NSString *)serviceID {
|
||||
return @"com.google.santa.syncservice";
|
||||
}
|
||||
|
||||
+ (MOLXPCConnection *)configuredConnection {
|
||||
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithName:[self serviceID]
|
||||
privileged:YES];
|
||||
c.remoteInterface = [self syncServiceInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
37
Source/common/SNTXPCSyncdInterface.h
Normal file
37
Source/common/SNTXPCSyncdInterface.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/// Copyright 2016 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 "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by santactl and utilized by santad
|
||||
@protocol SNTSyncdXPC
|
||||
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
|
||||
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
|
||||
reply:(void (^)(SNTBundleEventAction))reply;
|
||||
- (void)isFCMListening:(void (^)(BOOL))reply;
|
||||
@end
|
||||
|
||||
@interface SNTXPCSyncdInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)syncdInterface;
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2016 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.
|
||||
@@ -12,22 +12,21 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTKernelCommon.h"
|
||||
#import "Source/common/SNTXPCSyncdInterface.h"
|
||||
|
||||
@class SNTCachedDecision;
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
///
|
||||
/// Logs execution and file write events to syslog
|
||||
///
|
||||
@interface SNTEventLog : NSObject
|
||||
@implementation SNTXPCSyncdInterface
|
||||
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties;
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties;
|
||||
+ (NSXPCInterface *)syncdInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message;
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
- (void)saveDecisionDetails:(SNTCachedDecision *)cd;
|
||||
- (void)logDeniedExecution:(SNTCachedDecision *)cd withMessage:(santa_message_t)message;
|
||||
- (void)logAllowedExecution:(santa_message_t)message;
|
||||
return r;
|
||||
}
|
||||
|
||||
@end
|
||||
106
Source/common/SNTXPCUnprivilegedControlInterface.h
Normal file
106
Source/common/SNTXPCUnprivilegedControlInterface.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTKernelCommon.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@class MOLXPCConnection;
|
||||
|
||||
///
|
||||
/// Protocol implemented by santad and utilized by santactl (unprivileged operations)
|
||||
///
|
||||
@protocol SNTUnprivilegedDaemonControlXPC
|
||||
|
||||
///
|
||||
/// Kernel ops
|
||||
///
|
||||
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
|
||||
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
|
||||
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;
|
||||
- (void)driverConnectionEstablished:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary,
|
||||
int64_t certificate,
|
||||
int64_t compiler,
|
||||
int64_t transitive))reply;
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
|
||||
///
|
||||
/// Decision ops
|
||||
///
|
||||
|
||||
///
|
||||
/// @param filePath A Path to the file, can be nil.
|
||||
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
|
||||
/// be calculated by this method from the filePath.
|
||||
/// @param certificateSHA256 A SHA256 hash of the signing certificate, can be nil.
|
||||
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
|
||||
/// returned. Binary rules take precedence over cert rules.
|
||||
///
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTEventState))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
///
|
||||
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply;
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply;
|
||||
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
|
||||
- (void)syncCleanRequired:(void (^)(BOOL))reply;
|
||||
- (void)enableBundles:(void (^)(BOOL))reply;
|
||||
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// GUI Ops
|
||||
///
|
||||
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
|
||||
|
||||
///
|
||||
/// Syncd Ops
|
||||
///
|
||||
- (void)pushNotifications:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Bundle Ops
|
||||
///
|
||||
- (void)syncBundleEvent:(SNTStoredEvent *)event relatedEvents:(NSArray<SNTStoredEvent *> *)events;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTXPCUnprivilegedControlInterface : NSObject
|
||||
|
||||
///
|
||||
/// Returns an initialized NSXPCInterface for the SNTUnprivilegedDaemonControlXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
///
|
||||
+ (NSXPCInterface *)controlInterface;
|
||||
|
||||
///
|
||||
/// Internal method used to initialize the control interface
|
||||
///
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r;
|
||||
|
||||
@end
|
||||
38
Source/common/SNTXPCUnprivilegedControlInterface.m
Normal file
38
Source/common/SNTXPCUnprivilegedControlInterface.m
Normal file
@@ -0,0 +1,38 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTXPCUnprivilegedControlInterface
|
||||
|
||||
+ (void)initializeControlInterface:(NSXPCInterface *)r {
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
|
||||
forSelector:@selector(syncBundleEvent:relatedEvents:)
|
||||
argumentIndex:1
|
||||
ofReply:NO];
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)controlInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTUnprivilegedDaemonControlXPC)];
|
||||
[self initializeControlInterface:r];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -20,10 +20,7 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
#define likely(x) __builtin_expect((x), 1)
|
||||
#define unlikely(x) __builtin_expect((x), 0)
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <IOKit/IOLib.h>
|
||||
@@ -33,19 +30,31 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#define panic(args...) printf(args); printf("\n"); abort()
|
||||
#define IOMalloc malloc
|
||||
#define IOMallocAligned(sz, alignment) malloc(sz);
|
||||
#define IOFree(addr, sz) free(addr)
|
||||
#define IOFreeAligned(addr, sz) free(addr)
|
||||
#define OSTestAndSet OSAtomicTestAndSet
|
||||
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
|
||||
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
|
||||
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif // KERNEL
|
||||
|
||||
/**
|
||||
A type to specialize to help SantaCache with its hashing.
|
||||
|
||||
The default works for numeric types with a multiplicative hash
|
||||
using a prime near to the golden ratio, per Knuth.
|
||||
*/
|
||||
template<typename T> uint64_t SantaCacheHasher(T const& t) {
|
||||
return t * 11400714819323198549UL;
|
||||
};
|
||||
|
||||
/**
|
||||
A somewhat simple, concurrent linked-list hash table intended for use in IOKit kernel extensions.
|
||||
Maps 64-bit unsigned integer keys to values.
|
||||
|
||||
The type used for keys must overload the == operator and a specialization of
|
||||
SantaCacheHasher must exist for it.
|
||||
|
||||
Enforces a maximum size by clearing all entries if a new value
|
||||
is added that would go over the maximum size declared at creation.
|
||||
@@ -53,7 +62,7 @@
|
||||
The number of buckets is calculated as `maximum_size` / `per_bucket`
|
||||
rounded up to the next power of 2. Locking is done per-bucket.
|
||||
*/
|
||||
template<class T> class SantaCache {
|
||||
template<typename KeyT, typename ValueT> class SantaCache {
|
||||
public:
|
||||
/**
|
||||
Initialize a newly created cache.
|
||||
@@ -65,12 +74,13 @@ template<class T> class SantaCache {
|
||||
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 < 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));
|
||||
buckets_ = (struct bucket *)IOMalloc(bucket_count_ * sizeof(struct bucket));
|
||||
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);
|
||||
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
@@ -79,20 +89,21 @@ template<class T> class SantaCache {
|
||||
*/
|
||||
~SantaCache() {
|
||||
clear();
|
||||
IOFree(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
IOFreeAligned(buckets_, bucket_count_ * sizeof(struct bucket));
|
||||
}
|
||||
|
||||
/**
|
||||
Get an element from the cache. Returns zero_ if item doesn't exist.
|
||||
*/
|
||||
T get(uint64_t key) {
|
||||
ValueT get(KeyT key) {
|
||||
struct bucket *bucket = &buckets_[hash(key)];
|
||||
lock(bucket);
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
if (entry->key == key) {
|
||||
ValueT val = entry->value;
|
||||
unlock(bucket);
|
||||
return entry->value;
|
||||
return val;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
@@ -103,74 +114,40 @@ template<class T> class SantaCache {
|
||||
/**
|
||||
Set an element in the cache.
|
||||
|
||||
@note If the cache is full when this is called, this will empty the cache before
|
||||
inserting the new value.
|
||||
@note If the cache is full when this is called, this will
|
||||
empty the cache before inserting the new value.
|
||||
|
||||
@return if an existing value was replaced, the previous value, otherwise zero_
|
||||
@param key The key.
|
||||
@param value The value with parameterized type.
|
||||
|
||||
@return true if the value was set.
|
||||
*/
|
||||
T set(uint64_t key, T value) {
|
||||
struct bucket *bucket = &buckets_[hash(key)];
|
||||
lock(bucket);
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
struct entry *previous_entry = nullptr;
|
||||
while (entry != nullptr) {
|
||||
if (entry->key == key) {
|
||||
T existing_value = entry->value;
|
||||
entry->value = value;
|
||||
bool set(const KeyT& key, const ValueT& value) {
|
||||
return set(key, value, {}, false);
|
||||
}
|
||||
|
||||
if (value == zero_) {
|
||||
if (previous_entry != nullptr) {
|
||||
previous_entry->next = entry->next;
|
||||
} else {
|
||||
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
|
||||
}
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
OSDecrementAtomic(&count_);
|
||||
}
|
||||
/**
|
||||
Set an element in the cache.
|
||||
|
||||
unlock(bucket);
|
||||
return existing_value;
|
||||
}
|
||||
previous_entry = entry;
|
||||
entry = entry->next;
|
||||
}
|
||||
@note If the cache is full when this is called, this will
|
||||
empty the cache before inserting the new value.
|
||||
|
||||
// If value is zero_, we're clearing but there's nothing to clear
|
||||
// so we don't need to do anything else.
|
||||
if (value == zero_) {
|
||||
unlock(bucket);
|
||||
return zero_;
|
||||
}
|
||||
@param key The key.
|
||||
@param value The value with parameterized type.
|
||||
@param previous_value the new value will only be set if this
|
||||
parameter is equal to the existing value in the cache.
|
||||
This allows set to become a CAS operation.
|
||||
|
||||
// Check that adding this new item won't take the cache over its maximum size.
|
||||
if (count_ + 1 > max_size_) {
|
||||
unlock(bucket);
|
||||
lock(&clear_bucket_);
|
||||
// Check again in case clear has already run while waiting for lock
|
||||
if (count_ + 1 > max_size_) {
|
||||
clear();
|
||||
}
|
||||
lock(bucket);
|
||||
unlock(&clear_bucket_);
|
||||
}
|
||||
|
||||
// Allocate a new entry, set the key and value, then set the next pointer as the current
|
||||
// first entry in the bucket then make this new entry the first in the bucket.
|
||||
struct entry *new_entry = (struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
new_entry->key = key;
|
||||
new_entry->value = value;
|
||||
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
|
||||
OSIncrementAtomic(&count_);
|
||||
|
||||
unlock(bucket);
|
||||
return zero_;
|
||||
@return true if the value was set
|
||||
*/
|
||||
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(uint64_t key) {
|
||||
inline void remove(const KeyT& key) {
|
||||
set(key, zero_);
|
||||
}
|
||||
|
||||
@@ -210,10 +187,47 @@ template<class T> class SantaCache {
|
||||
return count_;
|
||||
}
|
||||
|
||||
/**
|
||||
Fill in the per_bucket_counts array with the number of entries in each bucket.
|
||||
|
||||
The per_buckets_count array will contain the per-bucket counts, up to the number
|
||||
in array_size. The start_bucket parameter will determine which bucket to start off
|
||||
with and upon return will contain either 0 if no buckets are remaining or the next
|
||||
bucket to begin with when called again.
|
||||
*/
|
||||
void bucket_counts(uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
|
||||
if (per_bucket_counts == nullptr || array_size == nullptr || start_bucket == nullptr) return;
|
||||
|
||||
uint64_t start = *start_bucket;
|
||||
if (start >= bucket_count_) {
|
||||
*start_bucket = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t size = *array_size;
|
||||
if (start + size > bucket_count_) size = bucket_count_ - start;
|
||||
|
||||
for (uint16_t i = 0; i < size; ++i) {
|
||||
uint16_t count = 0;
|
||||
struct bucket *bucket = &buckets_[start++];
|
||||
lock(bucket);
|
||||
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
while (entry != nullptr) {
|
||||
if (entry->value != zero_) ++count;
|
||||
entry = entry->next;
|
||||
}
|
||||
unlock(bucket);
|
||||
per_bucket_counts[i] = count;
|
||||
}
|
||||
|
||||
*array_size = size;
|
||||
*start_bucket = (start >= bucket_count_) ? 0 : start;
|
||||
}
|
||||
|
||||
private:
|
||||
struct entry {
|
||||
uint64_t key;
|
||||
T value;
|
||||
KeyT key;
|
||||
ValueT value;
|
||||
struct entry *next;
|
||||
};
|
||||
|
||||
@@ -223,6 +237,90 @@ template<class T> class SantaCache {
|
||||
struct entry *head;
|
||||
};
|
||||
|
||||
/**
|
||||
Set an element in the cache.
|
||||
|
||||
@note If the cache is full when this is called, this will
|
||||
empty the cache before inserting the new value.
|
||||
|
||||
@param key The key
|
||||
@param value The value with parameterized type
|
||||
@param previous_value If has_prev_value is true, the new value will only
|
||||
be set if this parameter is equal to the existing value in the cache.
|
||||
This allows set to become a CAS operation.
|
||||
@param has_prev_value Pass true if previous_value should be used.
|
||||
|
||||
@return true if the entry was set, false if it was not
|
||||
*/
|
||||
bool set(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);
|
||||
struct entry *previous_entry = nullptr;
|
||||
while (entry != nullptr) {
|
||||
if (entry->key == key) {
|
||||
ValueT existing_value = entry->value;
|
||||
|
||||
if (has_prev_value && previous_value != existing_value) {
|
||||
unlock(bucket);
|
||||
return false;
|
||||
}
|
||||
|
||||
entry->value = value;
|
||||
|
||||
if (value == zero_) {
|
||||
if (previous_entry != nullptr) {
|
||||
previous_entry->next = entry->next;
|
||||
} else {
|
||||
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
|
||||
}
|
||||
IOFreeAligned(entry, sizeof(struct entry));
|
||||
OSDecrementAtomic(&count_);
|
||||
}
|
||||
|
||||
unlock(bucket);
|
||||
return true;
|
||||
}
|
||||
previous_entry = entry;
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
// If value is zero_, we're clearing but there's nothing to clear
|
||||
// so we don't need to do anything else. Alternatively, if has_prev_value
|
||||
// is true and is not zero_ we don't want to set a value.
|
||||
if (value == zero_ || (has_prev_value && previous_value != zero_)) {
|
||||
unlock(bucket);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that adding this new item won't take the cache
|
||||
// over its maximum size.
|
||||
if (count_ + 1 > max_size_) {
|
||||
unlock(bucket);
|
||||
lock(&clear_bucket_);
|
||||
// Check again in case clear has already run while waiting for lock
|
||||
if (count_ + 1 > max_size_) {
|
||||
clear();
|
||||
}
|
||||
lock(bucket);
|
||||
unlock(&clear_bucket_);
|
||||
}
|
||||
|
||||
// Allocate a new entry, set the key and value, then put this new entry at
|
||||
// the head of this bucket's linked list.
|
||||
struct entry *new_entry = (struct entry *)IOMallocAligned(sizeof(struct entry), 2);
|
||||
bzero(new_entry, sizeof(struct entry));
|
||||
new_entry->key = key;
|
||||
new_entry->value = value;
|
||||
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
|
||||
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
|
||||
OSIncrementAtomic(&count_);
|
||||
|
||||
unlock(bucket);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
Lock a bucket. Spins until the lock is acquired.
|
||||
*/
|
||||
@@ -249,7 +347,7 @@ template<class T> class SantaCache {
|
||||
/**
|
||||
Holder for a 'zero' entry for the current type
|
||||
*/
|
||||
T zero_ = {};
|
||||
const ValueT zero_ = {};
|
||||
|
||||
/**
|
||||
Special bucket used when automatically clearing due to size
|
||||
@@ -260,14 +358,14 @@ template<class T> class SantaCache {
|
||||
|
||||
/**
|
||||
Hash a key to determine which bucket it belongs in.
|
||||
|
||||
Multiplicative hash using a prime near to the golden ratio, per Knuth.
|
||||
This seems to have good bucket distribution generally and for the range of
|
||||
values we expect to see.
|
||||
*/
|
||||
inline uint64_t hash(uint64_t input) const {
|
||||
return (input * 11400714819323198549ul) % bucket_count_;
|
||||
inline uint64_t hash(KeyT input) const {
|
||||
return SantaCacheHasher<KeyT>(input) % bucket_count_;
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef KERNEL
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|
||||
286
Source/common/SantaCacheTest.mm
Normal file
286
Source/common/SantaCacheTest.mm
Normal file
@@ -0,0 +1,286 @@
|
||||
/// Copyright 2016 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 <XCTest/XCTest.h>
|
||||
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/common/SantaCache.h"
|
||||
|
||||
@interface SantaCacheTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SantaCacheTest
|
||||
|
||||
- (void)setUp {
|
||||
self.continueAfterFailure = NO;
|
||||
}
|
||||
|
||||
- (void)testSetAndGet {
|
||||
auto sut = SantaCache<uint64_t, uint64_t>();
|
||||
|
||||
sut.set(72057611258548992llu, 10000192);
|
||||
XCTAssertEqual(sut.get(72057611258548992llu), 10000192);
|
||||
}
|
||||
|
||||
- (void)testCacheRemove {
|
||||
auto sut = SantaCache<uint64_t, uint64_t>();
|
||||
|
||||
sut.set(0xDEADBEEF, 42);
|
||||
sut.remove(0xDEADBEEF);
|
||||
|
||||
XCTAssertEqual(sut.get(0xDEADBEEF), 0);
|
||||
}
|
||||
|
||||
- (void)testBucketGrowCopy {
|
||||
auto sut = SantaCache<uint64_t, uint64_t>();
|
||||
|
||||
sut.set(386, 42);
|
||||
sut.set(2434, 42);
|
||||
|
||||
XCTAssertEqual(sut.get(386), 42);
|
||||
XCTAssertEqual(sut.get(2434), 42);
|
||||
}
|
||||
|
||||
- (void)testBucketShrinkCopy {
|
||||
auto sut = SantaCache<uint64_t, uint64_t>(100, 1);
|
||||
|
||||
sut.set(386, 42);
|
||||
sut.set(2434, 42);
|
||||
sut.set(4482, 42);
|
||||
|
||||
sut.remove(2434);
|
||||
|
||||
XCTAssertEqual(sut.get(386), 42);
|
||||
XCTAssertEqual(sut.get(2434), 0);
|
||||
XCTAssertEqual(sut.get(4482), 42);
|
||||
}
|
||||
|
||||
- (void)testCacheResetAtLimit {
|
||||
auto sut = SantaCache<uint64_t, uint64_t>(5);
|
||||
|
||||
sut.set(1, 42);
|
||||
sut.set(2, 42);
|
||||
sut.set(3, 42);
|
||||
sut.set(4, 42);
|
||||
sut.set(5, 42);
|
||||
XCTAssertEqual(sut.get(3), 42);
|
||||
sut.set(6, 42);
|
||||
XCTAssertEqual(sut.get(3), 0);
|
||||
XCTAssertEqual(sut.get(6), 42);
|
||||
}
|
||||
|
||||
// Helper to test bucket distributions for uint64_t/uint64_t combinations.
|
||||
- (void)distributionTestHelper:(SantaCache<uint64_t, uint64_t> *)sut bucketRatio:(int)br {
|
||||
uint16_t count[512];
|
||||
uint16_t array_size = 512;
|
||||
uint64_t start_bucket = 0;
|
||||
std::vector<uint16_t> per_bucket;
|
||||
do {
|
||||
sut->bucket_counts(count, &array_size, &start_bucket);
|
||||
for (int i = 0; i < array_size; ++i) {
|
||||
per_bucket.push_back(count[i]);
|
||||
}
|
||||
} while (start_bucket > 0);
|
||||
|
||||
// Calculate mean
|
||||
double mean = std::accumulate(per_bucket.begin(), per_bucket.end(), 0.0) / per_bucket.size();
|
||||
XCTAssertLessThanOrEqual(mean, br, @"Mean per-bucket count is greater than %d", br);
|
||||
|
||||
// Calculate stdev
|
||||
double accum = 0.0;
|
||||
std::for_each(per_bucket.begin(), per_bucket.end(), [&](const double d) {
|
||||
accum += (d - mean) * (d - mean);
|
||||
});
|
||||
double stddev = sqrt(accum / (per_bucket.size() - 1));
|
||||
double maxStdDev = (double)br / 2;
|
||||
XCTAssertLessThanOrEqual(stddev, maxStdDev,
|
||||
@"Standard deviation between buckets is greater than %f", maxStdDev);
|
||||
}
|
||||
|
||||
- (void)testDistributionRandomKeys {
|
||||
const int bucket_ratio = 5;
|
||||
auto sut = new SantaCache<uint64_t, uint64_t>(5000, bucket_ratio);
|
||||
|
||||
// Fill the cache with random keys, all set to 1.
|
||||
for (int i = 0; i < 4000; ++i) {
|
||||
sut->set((uint64_t)arc4random() << 32 | arc4random(), 1);
|
||||
}
|
||||
|
||||
[self distributionTestHelper:sut bucketRatio:bucket_ratio];
|
||||
}
|
||||
|
||||
- (void)testDistributionMontonicKeys {
|
||||
const int bucket_ratio = 5;
|
||||
auto sut = new SantaCache<uint64_t, uint64_t>(5000, bucket_ratio);
|
||||
|
||||
// Fill the cache with monotonic keys, all set to 1.
|
||||
for (int i = 0; i < 4000; ++i) {
|
||||
sut->set(i, 1);
|
||||
}
|
||||
|
||||
[self distributionTestHelper:sut bucketRatio:bucket_ratio];
|
||||
}
|
||||
|
||||
- (void)testThreading {
|
||||
auto sut = new SantaCache<uint64_t, uint64_t>();
|
||||
|
||||
for (int x = 0; x < 200; ++x) {
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
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);
|
||||
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);
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
XCTFail("Timed out while setting values for test");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10000; ++i) XCTAssertEqual(sut->get(i), 10000 - i);
|
||||
}
|
||||
|
||||
delete sut;
|
||||
}
|
||||
|
||||
- (void)testCount {
|
||||
auto sut = SantaCache<uint64_t, int>();
|
||||
|
||||
XCTAssertEqual(sut.count(), 0);
|
||||
|
||||
sut.set(4012, 42);
|
||||
sut.set(42, 0);
|
||||
sut.set(0x8BADF00D, 40010);
|
||||
|
||||
XCTAssertEqual(sut.count(), 2);
|
||||
}
|
||||
|
||||
- (void)testDoubles {
|
||||
auto sut = SantaCache<double, double>();
|
||||
|
||||
sut.set(3.14, 2.718281);
|
||||
sut.set(1.41429, 2.5029);
|
||||
sut.set(4.6692, 1.2020569);
|
||||
sut.set(1.61803398, 0.57721);
|
||||
|
||||
XCTAssertEqual(sut.count(), 4);
|
||||
XCTAssertEqual(sut.get(3.14), 2.718281);
|
||||
XCTAssertEqual(sut.get(1.41429), 2.5029);
|
||||
XCTAssertEqual(sut.get(4.6692), 1.2020569);
|
||||
XCTAssertEqual(sut.get(1.61803398), 0.57721);
|
||||
|
||||
XCTAssertEqual(sut.get(5.5555), 0);
|
||||
XCTAssertEqual(sut.get(3.1459124), 0);
|
||||
}
|
||||
|
||||
template<> uint64_t SantaCacheHasher<std::string>(std::string const& s) {
|
||||
return std::hash<std::string>{}(s);
|
||||
}
|
||||
|
||||
- (void)testStrings {
|
||||
auto sut = SantaCache<std::string, std::string>();
|
||||
|
||||
std::string s1 = "foo";
|
||||
std::string s2 = "bar";
|
||||
|
||||
sut.set(s1, "deadbeef");
|
||||
sut.set(s2, "feedface");
|
||||
|
||||
XCTAssertEqual(sut.count(), 2);
|
||||
XCTAssertEqual(sut.get(s1), "deadbeef");
|
||||
XCTAssertEqual(sut.get(s2), "feedface");
|
||||
|
||||
sut.remove(s2);
|
||||
|
||||
XCTAssertTrue(sut.get(s2).empty());
|
||||
}
|
||||
|
||||
- (void)testCompareAndSwap {
|
||||
auto sut = SantaCache<uint64_t, uint64_t>(100, 2);
|
||||
|
||||
sut.set(1, 42);
|
||||
sut.set(1, 666, 1);
|
||||
sut.set(1, 666, 0);
|
||||
XCTAssertEqual(sut.get(1), 42);
|
||||
|
||||
sut.set(1, 0);
|
||||
XCTAssertEqual(sut.get(1), 0);
|
||||
|
||||
sut.set(1, 42, 1);
|
||||
XCTAssertEqual(sut.get(1), 0);
|
||||
|
||||
sut.set(1, 42, 0);
|
||||
XCTAssertEqual(sut.get(1), 42);
|
||||
|
||||
sut.set(1, 0, 666);
|
||||
XCTAssertEqual(sut.get(1), 42);
|
||||
sut.set(1, 0, 42);
|
||||
XCTAssertEqual(sut.get(1), 0);
|
||||
}
|
||||
|
||||
struct S {
|
||||
uint64_t first_val;
|
||||
uint64_t second_val;
|
||||
|
||||
bool operator==(const S& rhs) {
|
||||
return first_val == rhs.first_val && second_val == rhs.second_val;
|
||||
}
|
||||
};
|
||||
template<> uint64_t SantaCacheHasher<S>(S const& s) {
|
||||
return SantaCacheHasher<uint64_t>(s.first_val) ^ (SantaCacheHasher<uint64_t>(s.second_val) << 1);
|
||||
}
|
||||
|
||||
- (void)testStructKeys {
|
||||
auto sut = SantaCache<S, uint64_t>(10, 2);
|
||||
|
||||
S s1 = {1024, 2048};
|
||||
S s2 = {4096, 8192};
|
||||
S s3 = {16384, 32768};
|
||||
sut.set(s1, 10);
|
||||
sut.set(s2, 20);
|
||||
sut.set(s3, 30);
|
||||
|
||||
XCTAssertEqual(sut.get(s1), 10);
|
||||
XCTAssertEqual(sut.get(s2), 20);
|
||||
XCTAssertEqual(sut.get(s3), 30);
|
||||
}
|
||||
|
||||
- (void)testBucketCounts {
|
||||
auto sut = new SantaCache<uint64_t, uint64_t>(UINT16_MAX, 1);
|
||||
|
||||
// These tests verify that the bucket_counts() function can't be abused
|
||||
// with integer {over,under}flow issues in the input or going out-of-bounds
|
||||
// on the buckets array.
|
||||
uint16_t size = 2048;
|
||||
uint64_t start = (UINT64_MAX - 2047);
|
||||
uint16_t per_bucket_counts[2048];
|
||||
sut->bucket_counts(per_bucket_counts, &size, &start);
|
||||
XCTAssertEqual(start, 0, @"Check a high start can't overflow");
|
||||
|
||||
size = UINT16_MAX;
|
||||
start = UINT16_MAX - 1;
|
||||
sut->bucket_counts(per_bucket_counts, &size, &start);
|
||||
XCTAssertEqual(start, 0, @"Check a large size can't overflow");
|
||||
}
|
||||
|
||||
@end
|
||||
BIN
Source/common/testdata/32bitplist
vendored
Executable file
BIN
Source/common/testdata/32bitplist
vendored
Executable file
Binary file not shown.
52
Source/common/testdata/BundleExample.app/Contents/Info.plist
vendored
Normal file
52
Source/common/testdata/BundleExample.app/Contents/Info.plist
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>16F73</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>BundleExample</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa.BundleExample</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>BundleExample</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>15E60</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.11</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0731</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.12</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017 Google. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
Source/common/testdata/BundleExample.app/Contents/MacOS/BundleExample
vendored
Executable file
BIN
Source/common/testdata/BundleExample.app/Contents/MacOS/BundleExample
vendored
Executable file
Binary file not shown.
52
Source/common/testdata/DirectoryBundle/Contents/Info.plist
vendored
Normal file
52
Source/common/testdata/DirectoryBundle/Contents/Info.plist
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>16F73</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>DirectoryBundle</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa.DirectoryBundle</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>DirectoryBundle</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>15E60</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.11</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0731</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.12</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2018 Google. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
Source/common/testdata/DirectoryBundle/Contents/MacOS/DirectoryBundle
vendored
Executable file
BIN
Source/common/testdata/DirectoryBundle/Contents/MacOS/DirectoryBundle
vendored
Executable file
Binary file not shown.
52
Source/common/testdata/DirectoryBundle/Contents/Resources/BundleExample.app/Contents/Info.plist
vendored
Normal file
52
Source/common/testdata/DirectoryBundle/Contents/Resources/BundleExample.app/Contents/Info.plist
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>16F73</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>BundleExample</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.google.santa.BundleExample</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>BundleExample</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>15E60</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.11</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0731</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>7D1014</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.12</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017 Google. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
Source/common/testdata/DirectoryBundle/Contents/Resources/BundleExample.app/Contents/MacOS/BundleExample
vendored
Executable file
BIN
Source/common/testdata/DirectoryBundle/Contents/Resources/BundleExample.app/Contents/MacOS/BundleExample
vendored
Executable file
Binary file not shown.
@@ -1,535 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SantaDecisionManager.h"
|
||||
|
||||
#define super OSObject
|
||||
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
|
||||
|
||||
#pragma mark Object Lifecycle
|
||||
|
||||
bool SantaDecisionManager::init() {
|
||||
if (!super::init()) return false;
|
||||
|
||||
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
|
||||
sdm_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
|
||||
decision_cache_ = new SantaCache<uint64_t>(10000, 2);
|
||||
vnode_pid_map_ = new SantaCache<uint64_t>(2000, 5);
|
||||
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
if (!decision_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
if (!log_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
client_pid_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::free() {
|
||||
delete decision_cache_;
|
||||
delete vnode_pid_map_;
|
||||
|
||||
if (decision_dataqueue_lock_) {
|
||||
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
|
||||
decision_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (log_dataqueue_lock_) {
|
||||
lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
|
||||
log_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_attr_) {
|
||||
lck_attr_free(sdm_lock_attr_);
|
||||
sdm_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_) {
|
||||
lck_grp_free(sdm_lock_grp_);
|
||||
sdm_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_attr_) {
|
||||
lck_grp_attr_free(sdm_lock_grp_attr_);
|
||||
sdm_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
|
||||
OSSafeReleaseNULL(decision_dataqueue_);
|
||||
OSSafeReleaseNULL(log_dataqueue_);
|
||||
|
||||
super::free();
|
||||
}
|
||||
|
||||
#pragma mark Client Management
|
||||
|
||||
void SantaDecisionManager::ConnectClient(pid_t pid) {
|
||||
if (!pid) return;
|
||||
|
||||
client_pid_ = pid;
|
||||
|
||||
// Any decisions made while the daemon wasn't
|
||||
// connected should be cleared
|
||||
ClearCache();
|
||||
|
||||
failed_decision_queue_requests_ = 0;
|
||||
failed_log_queue_requests_ = 0;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DisconnectClient(bool itDied) {
|
||||
if (client_pid_ < 1) return;
|
||||
client_pid_ = 0;
|
||||
|
||||
// Ask santad to shutdown, in case it's running.
|
||||
if (!itDied) {
|
||||
auto message = new santa_message_t;
|
||||
message->action = ACTION_REQUEST_SHUTDOWN;
|
||||
PostToDecisionQueue(message);
|
||||
delete message;
|
||||
decision_dataqueue_->setNotificationPort(nullptr);
|
||||
} else {
|
||||
// If the client died, reset the data queues so when it reconnects
|
||||
// it doesn't get swamped straight away.
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->release();
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->release();
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::ClientConnected() const {
|
||||
if (client_pid_ <= 0) return false;
|
||||
auto p = proc_find(client_pid_);
|
||||
auto is_exiting = false;
|
||||
if (p) {
|
||||
is_exiting = proc_exiting(p);
|
||||
proc_rele(p);
|
||||
}
|
||||
return (client_pid_ > 0 && !is_exiting);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::SetLogPort(mach_port_t port) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
|
||||
return decision_dataqueue_->getMemoryDescriptor();
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
|
||||
return log_dataqueue_->getMemoryDescriptor();
|
||||
}
|
||||
|
||||
#pragma mark Listener Control
|
||||
|
||||
kern_return_t SantaDecisionManager::StartListener() {
|
||||
vnode_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
|
||||
if (!vnode_listener_) return kIOReturnInternalError;
|
||||
|
||||
fileop_listener_ = kauth_listen_scope(
|
||||
KAUTH_SCOPE_FILEOP, fileop_scope_callback, reinterpret_cast<void *>(this));
|
||||
if (!fileop_listener_) return kIOReturnInternalError;
|
||||
|
||||
LOGD("Listeners started.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
kern_return_t SantaDecisionManager::StopListener() {
|
||||
kauth_unlisten_scope(vnode_listener_);
|
||||
vnode_listener_ = nullptr;
|
||||
|
||||
kauth_unlisten_scope(fileop_listener_);
|
||||
fileop_listener_ = nullptr;
|
||||
|
||||
// Wait for any active invocations to finish before returning
|
||||
do {
|
||||
IOSleep(5);
|
||||
} while (listener_invocations_);
|
||||
|
||||
// Delete any cached decisions
|
||||
ClearCache();
|
||||
|
||||
LOGD("Listeners stopped.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
#pragma mark Cache Management
|
||||
|
||||
void SantaDecisionManager::AddToCache(
|
||||
uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
|
||||
// If a previous entry was not found and the new entry is not `REQUEST_BINARY`, remove the
|
||||
// existing entry. This is to prevent adding an ALLOW to the cache after a write has occurred.
|
||||
if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
|
||||
decision_cache_->remove(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
|
||||
decision_cache_->remove(identifier);
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::CacheCount() const {
|
||||
return decision_cache_->count();
|
||||
}
|
||||
|
||||
void SantaDecisionManager::ClearCache() {
|
||||
decision_cache_->clear();
|
||||
}
|
||||
|
||||
#pragma mark Decision Fetching
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
|
||||
auto result = ACTION_UNSET;
|
||||
uint64_t decision_time = 0;
|
||||
|
||||
uint64_t cache_val = decision_cache_->get(identifier);
|
||||
if (cache_val == 0) return result;
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
result = (santa_action_t)(cache_val >> 56);
|
||||
decision_time = (cache_val & ~(0xFF00000000000000));
|
||||
|
||||
if (RESPONSE_VALID(result)) {
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
auto diff_time = GetCurrentUptime();
|
||||
if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
|
||||
diff_time = 0;
|
||||
} else {
|
||||
diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
|
||||
}
|
||||
if (decision_time < diff_time) {
|
||||
decision_cache_->remove(identifier);
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
|
||||
auto return_action = ACTION_UNSET;
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Add pending request to cache, to be replaced by daemon with actual response
|
||||
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
|
||||
|
||||
// Send request to daemon...
|
||||
if (!PostToDecisionQueue(message)) {
|
||||
OSIncrementAtomic(&failed_decision_queue_requests_);
|
||||
if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
client_pid_ = 0;
|
||||
}
|
||||
LOGE("Failed to queue request for %s.", message->path);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
do {
|
||||
IOSleep(kRequestLoopSleepMilliseconds);
|
||||
return_action = GetFromCache(identifier);
|
||||
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
|
||||
} while (!RESPONSE_VALID(return_action) && ClientConnected());
|
||||
|
||||
// If response is still not valid, the daemon exited
|
||||
if (!RESPONSE_VALID(return_action)) {
|
||||
LOGE("Daemon process did not respond correctly. Allowing executions "
|
||||
"until it comes back. Executable path: %s", message->path);
|
||||
RemoveFromCache(identifier);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id);
|
||||
|
||||
// If item was in cache return it.
|
||||
if (RESPONSE_VALID(return_action)) return return_action;
|
||||
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
int name_len = MAXPATHLEN;
|
||||
if (vn_getpath(vp, path, &name_len) != 0) {
|
||||
path[0] = '\0';
|
||||
}
|
||||
|
||||
auto message = NewMessage(cred);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
message->action = ACTION_REQUEST_BINARY;
|
||||
message->vnode_id = vnode_id;
|
||||
proc_name(message->ppid, message->pname, sizeof(message->pname));
|
||||
return_action = GetFromDaemon(message, vnode_id);
|
||||
delete message;
|
||||
return return_action;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (OSCompareAndSwap(0, 1, &failed_log_queue_requests_)) {
|
||||
LOGW("Dropping log queue messages");
|
||||
}
|
||||
// If enqueue failed, pop an item off the queue and try again.
|
||||
uint32_t dataSize = 0;
|
||||
log_dataqueue_->dequeue(0, &dataSize);
|
||||
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
} else {
|
||||
OSCompareAndSwap(1, 0, &failed_log_queue_requests_);
|
||||
}
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
#pragma mark Invocation Tracking & PID comparison
|
||||
|
||||
void SantaDecisionManager::IncrementListenerInvocations() {
|
||||
OSIncrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DecrementListenerInvocations() {
|
||||
OSDecrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
#pragma mark Callbacks
|
||||
|
||||
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vfs_context_t ctx,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Only operate on regular files (not directories, symlinks, etc.).
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Get ID for the vnode and convert it to a string.
|
||||
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
char vnode_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
// Fetch decision
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
|
||||
|
||||
// If file has dirty blocks, remove from cache and deny. This would usually
|
||||
// be the case if a file has been written to and flushed but not yet
|
||||
// closed.
|
||||
if (vnode_hasdirtyblks(vp)) {
|
||||
RemoveFromCache(vnode_id);
|
||||
returnedAction = ACTION_RESPOND_DENY;
|
||||
}
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_ALLOW: {
|
||||
auto proc = vfs_context_proc(ctx);
|
||||
if (proc) {
|
||||
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);
|
||||
vnode_pid_map_->set(vnode_id, val);
|
||||
}
|
||||
return KAUTH_RESULT_ALLOW;
|
||||
}
|
||||
case ACTION_RESPOND_DENY:
|
||||
*errno = EPERM;
|
||||
return KAUTH_RESULT_DENY;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
// we don't break user's machines.
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (vp) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
auto vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
vfs_context_rele(context);
|
||||
|
||||
if (action == KAUTH_FILEOP_CLOSE) {
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
RemoveFromCache(vnode_id);
|
||||
} else if (action == KAUTH_FILEOP_EXEC) {
|
||||
auto message = NewMessage(nullptr);
|
||||
message->vnode_id = vnode_id;
|
||||
message->action = ACTION_NOTIFY_EXEC;
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
uint64_t val = vnode_pid_map_->get(vnode_id);
|
||||
if (val) {
|
||||
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
|
||||
message->pid = (val >> 32);
|
||||
message->ppid = (val & ~0xFFFFFFFF00000000);
|
||||
}
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out modifications to locations that are definitely
|
||||
// not useful or made by santad.
|
||||
if (proc_selfpid() != client_pid_ &&
|
||||
!strprefix(path, "/.") &&
|
||||
!strprefix(path, "/dev")) {
|
||||
auto message = NewMessage(nullptr);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
|
||||
proc_name(message->pid, message->pname, sizeof(message->pname));
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_CLOSE:
|
||||
message->action = ACTION_NOTIFY_WRITE;
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
message->action = ACTION_NOTIFY_RENAME;
|
||||
break;
|
||||
case KAUTH_FILEOP_LINK:
|
||||
message->action = ACTION_NOTIFY_LINK;
|
||||
break;
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
message->action = ACTION_NOTIFY_EXCHANGE;
|
||||
break;
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
message->action = ACTION_NOTIFY_DELETE;
|
||||
break;
|
||||
default:
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
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) {
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
vnode_t vp = nullptr;
|
||||
char *path = nullptr;
|
||||
char *new_path = nullptr;
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
case KAUTH_FILEOP_EXEC:
|
||||
vp = reinterpret_cast<vnode_t>(arg0);
|
||||
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
case KAUTH_FILEOP_LINK:
|
||||
path = reinterpret_cast<char *>(arg0);
|
||||
new_path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
default:
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
sdm->IncrementListenerInvocations();
|
||||
sdm->FileOpCallback(action, vp, path, new_path);
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (action & KAUTH_VNODE_ACCESS || idata == nullptr) {
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (action & KAUTH_VNODE_EXECUTE) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
reinterpret_cast<vnode_t>(arg1),
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
} else if (action & KAUTH_VNODE_WRITE_DATA) {
|
||||
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
sdm->IncrementListenerInvocations();
|
||||
char path[MAXPATHLEN];
|
||||
int pathlen = MAXPATHLEN;
|
||||
vn_getpath(vp, path, &pathlen);
|
||||
sdm->FileOpCallback(KAUTH_FILEOP_CLOSE, vp, path, nullptr);
|
||||
sdm->DecrementListenerInvocations();
|
||||
}
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user