mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9e5bf09a7 | ||
|
|
4ee3f281c3 | ||
|
|
462ce89d42 | ||
|
|
44117833c0 | ||
|
|
8b6e029da2 | ||
|
|
f183e246df | ||
|
|
c60a35f280 | ||
|
|
4f65965277 | ||
|
|
01e4e15b81 | ||
|
|
532cb37e0b |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -17,7 +17,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: bazel build --apple_generate_dsym -c opt :release && bazel build --apple_generate_dsym -c opt :release_driver
|
||||
- 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
|
||||
|
||||
6
BUILD
6
BUILD
@@ -60,9 +60,9 @@ set -e
|
||||
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*/bin/Source/santa_driver/santa_driver.zip >/dev/null
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-bin/Source/santa/Santa.zip >/dev/null
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*/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
|
||||
@@ -209,9 +209,9 @@ genrule(
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
"//Source/common:SantaCacheTest",
|
||||
"//Source/common:SNTFileInfoTest",
|
||||
"//Source/common:SNTPrefixTreeTest",
|
||||
"//Source/santa_driver:SantaCacheTest",
|
||||
"//Source/santactl:SNTCommandFileInfoTest",
|
||||
"//Source/santactl:SNTCommandSyncTest",
|
||||
"//Source/santad:SNTEventTableTest",
|
||||
|
||||
@@ -4,6 +4,21 @@ 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"],
|
||||
@@ -214,6 +229,7 @@ santa_unit_test(
|
||||
resources = [
|
||||
"testdata/bad_pagezero",
|
||||
"testdata/missing_pagezero",
|
||||
"testdata/32bitplist",
|
||||
],
|
||||
structured_resources = glob([
|
||||
"testdata/BundleExample.app/**",
|
||||
|
||||
@@ -173,6 +173,15 @@
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSystemExtension;
|
||||
|
||||
///
|
||||
/// Use an internal cache for decisions instead of relying on the caching
|
||||
/// mechanism built-in to the EndpointSecurity framework. This may increase
|
||||
/// performance, particularly when Santa is run alongside other system
|
||||
/// extensions.
|
||||
/// Has no effect if the system extension is not being used. Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSysxCache;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
///
|
||||
@@ -332,6 +341,14 @@
|
||||
///
|
||||
@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.
|
||||
///
|
||||
|
||||
@@ -79,11 +79,14 @@ 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";
|
||||
@@ -155,9 +158,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kEventLogPath : string,
|
||||
kEnableMachineIDDecoration : number,
|
||||
kEnableSystemExtension : number,
|
||||
kEnableSysxCache : number,
|
||||
kEnableForkAndExitLogging : number,
|
||||
kIgnoreOtherEndpointSecurityClients : number,
|
||||
kEnableDebugLogging : number,
|
||||
kEnableBackwardsCompatibleContentEncoding : number,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
@@ -329,6 +334,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableSysxCache {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableForkAndExitLogging {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -341,6 +350,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
@@ -576,6 +589,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)enableSysxCache {
|
||||
NSNumber *number = self.configState[kEnableSysxCache];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (BOOL)enableForkAndExitLogging {
|
||||
NSNumber *number = self.configState[kEnableForkAndExitLogging];
|
||||
return number ? [number boolValue] : NO;
|
||||
@@ -591,6 +609,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [number boolValue] || self.debugFlag;
|
||||
}
|
||||
|
||||
- (BOOL)enableBackwardsCompatibleContentEncoding {
|
||||
NSNumber *number = self.configState[kEnableBackwardsCompatibleContentEncoding];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
///
|
||||
|
||||
@@ -551,24 +551,51 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
for (uint32_t i = 0; i < ncmds; ++i) {
|
||||
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
|
||||
if (!cmdData) return nil;
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
|
||||
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
|
||||
if (is64) {
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT_64 && memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
} else {
|
||||
struct segment_command *lc = (struct segment_command *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT && memcmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
|
||||
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
|
||||
for (uint32_t i = 0; i < nsects; ++i) {
|
||||
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
|
||||
if (!sectData) return nil;
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
|
||||
uint64_t sectoffset, sectsize = 0;
|
||||
BOOL found = NO;
|
||||
if (is64) {
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
sectoffset = sect->offset;
|
||||
sectsize = sect->size;
|
||||
found = YES;
|
||||
}
|
||||
} else {
|
||||
struct section *sect = (struct section *)[sectData bytes];
|
||||
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
sectoffset = sect->offset;
|
||||
sectsize = sect->size;
|
||||
found = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(mhwo.offset + sectoffset,
|
||||
sectsize)];
|
||||
if (!plistData) return nil;
|
||||
NSDictionary *plist;
|
||||
plist = [NSPropertyListSerialization propertyListWithData:plistData
|
||||
|
||||
@@ -39,9 +39,8 @@
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
|
||||
XCTAssertEqualObjects(sut.path, @"/bin/ls");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/AppleFileServer"];
|
||||
XCTAssertEqualObjects(sut.path, @"/System/Library/CoreServices/AppleFileServer.app/"
|
||||
@"Contents/MacOS/AppleFileServer");
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/DirectoryService"];
|
||||
XCTAssertEqualObjects(sut.path, @"/usr/libexec/dspluginhelperd");
|
||||
}
|
||||
|
||||
- (void)testSHA1 {
|
||||
@@ -72,7 +71,6 @@
|
||||
XCTAssertTrue(sut.isExecutable);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
@@ -106,7 +104,7 @@
|
||||
}
|
||||
|
||||
- (void)testDylibs {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/libsqlite3.dylib"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/system/libsystem_platform.dylib"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isDylib);
|
||||
@@ -231,10 +229,16 @@
|
||||
}
|
||||
|
||||
- (void)testEmbeddedInfoPlist {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"32bitplist"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
XCTAssertEqualObjects([sut infoPlist][@"CFBundleShortVersionString"], @"1.0");
|
||||
XCTAssertEqualObjects([sut infoPlist][@"CFBundleIdentifier"], @"com.google.i386plist");
|
||||
|
||||
// csreq is installed on all machines with Xcode installed. If you're running these tests,
|
||||
// it should be available..
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/santa_driver/SantaCache.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
|
||||
@interface SantaCacheTest : XCTestCase
|
||||
@end
|
||||
BIN
Source/common/testdata/32bitplist
vendored
Executable file
BIN
Source/common/testdata/32bitplist
vendored
Executable file
Binary file not shown.
@@ -11,7 +11,6 @@ load("//:version.bzl", "SANTA_VERSION")
|
||||
cc_library(
|
||||
name = "santa_driver_lib",
|
||||
srcs = [
|
||||
"SantaCache.h",
|
||||
"SantaDecisionManager.cc",
|
||||
"SantaDecisionManager.h",
|
||||
"SantaDriver.cc",
|
||||
@@ -33,6 +32,7 @@ cc_library(
|
||||
"SANTA_VERSION=" + SANTA_VERSION,
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SNTKernelCommon",
|
||||
"//Source/common:SNTLoggingKernel",
|
||||
"//Source/common:SNTPrefixTreeKernel",
|
||||
@@ -40,15 +40,6 @@ cc_library(
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SantaCacheTest",
|
||||
srcs = [
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
],
|
||||
deps = ["//Source/common:SNTKernelCommon"],
|
||||
)
|
||||
|
||||
macos_kernel_extension(
|
||||
name = "santa_driver",
|
||||
bundle_id = "com.google.santa-driver",
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#include <sys/proc.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "Source/common/SantaCache.h"
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
#include "Source/santa_driver/SantaCache.h"
|
||||
|
||||
///
|
||||
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
|
||||
|
||||
@@ -86,9 +86,11 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
|
||||
SNTConfigurator *configurator = [SNTConfigurator configurator];
|
||||
|
||||
BOOL cachingEnabled = (![configurator enableSystemExtension] || [configurator enableSysxCache]);
|
||||
|
||||
// Kext status
|
||||
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
|
||||
if (![configurator enableSystemExtension]) {
|
||||
if (cachingEnabled) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] cacheCounts:^(uint64_t rootCache, uint64_t nonRootCache) {
|
||||
rootCacheCount = rootCache;
|
||||
@@ -206,8 +208,8 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
@"transitive_rules" : @(enableTransitiveRules),
|
||||
},
|
||||
} mutableCopy];
|
||||
if (![configurator enableSystemExtension]) {
|
||||
stats[@"kernel"] = @{
|
||||
if (cachingEnabled) {
|
||||
stats[@"cache"] = @{
|
||||
@"root_cache_count" : @(rootCacheCount),
|
||||
@"non_root_cache_count": @(nonRootCacheCount),
|
||||
};
|
||||
@@ -224,8 +226,8 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
if (![configurator enableSystemExtension]) {
|
||||
printf(">>> Kernel Info\n");
|
||||
if (cachingEnabled) {
|
||||
printf(">>> Cache Info\n");
|
||||
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
|
||||
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
|
||||
}
|
||||
|
||||
@@ -482,6 +482,9 @@ static void reachabilityHandler(
|
||||
syncState.daemonConn = self.daemonConn;
|
||||
syncState.daemon = self.daemon;
|
||||
|
||||
syncState.compressedContentEncoding =
|
||||
config.enableBackwardsCompatibleContentEncoding ? @"zlib" : @"deflate";
|
||||
|
||||
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
|
||||
return syncState;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
NSData *compressed = [requestBody zlibCompressed];
|
||||
if (compressed) {
|
||||
requestBody = compressed;
|
||||
[req setValue:@"deflate" forHTTPHeaderField:@"Content-Encoding"];
|
||||
[req setValue:self.syncState.compressedContentEncoding forHTTPHeaderField:@"Content-Encoding"];
|
||||
}
|
||||
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
@@ -78,4 +78,8 @@
|
||||
/// Reference to the serial operation queue used for accessing allowlistNotifications.
|
||||
@property(weak) NSOperationQueue *allowlistNotificationQueue;
|
||||
|
||||
/// The header value for ContentEncoding when sending compressed content.
|
||||
/// Either "deflate" (default) or "zlib".
|
||||
@property(copy) NSString *compressedContentEncoding;
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,6 +16,8 @@ objc_library(
|
||||
"EventProviders/SNTDriverManager.m",
|
||||
"EventProviders/SNTEndpointSecurityManager.h",
|
||||
"EventProviders/SNTEndpointSecurityManager.mm",
|
||||
"EventProviders/SNTCachingEndpointSecurityManager.h",
|
||||
"EventProviders/SNTCachingEndpointSecurityManager.mm",
|
||||
"EventProviders/SNTEventProvider.h",
|
||||
"Logs/SNTEventLog.h",
|
||||
"Logs/SNTEventLog.m",
|
||||
@@ -50,6 +52,7 @@ objc_library(
|
||||
"IOKit",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
@@ -115,6 +118,7 @@ santa_unit_test(
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
|
||||
|
||||
@interface SNTCachingEndpointSecurityManager : SNTEndpointSecurityManager
|
||||
@end
|
||||
@@ -0,0 +1,190 @@
|
||||
/// Copyright 2021 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
|
||||
|
||||
#import "Source/common/SantaCache.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
uint64_t GetCurrentUptime() {
|
||||
return clock_gettime_nsec_np(CLOCK_MONOTONIC);
|
||||
}
|
||||
template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t) {
|
||||
return (SantaCacheHasher<uint64_t>(t.fsid) << 1) ^ SantaCacheHasher<uint64_t>(t.fileid);
|
||||
}
|
||||
|
||||
@implementation SNTCachingEndpointSecurityManager {
|
||||
std::atomic<bool> _compilerPIDs[PID_MAX];
|
||||
SantaCache<santa_vnode_id_t, uint64_t> *_decisionCache;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// TODO(rah): Consider splitting into root/non-root cache
|
||||
_decisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_decisionCache) delete _decisionCache;
|
||||
}
|
||||
|
||||
- (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
|
||||
auto vnode_id = [self vnodeIDForFile:m->event.exec.target->executable];
|
||||
while (true) {
|
||||
// Check to see if item is in cache
|
||||
auto return_action = [self checkCache:vnode_id];
|
||||
|
||||
// If item was in cache with a valid response, return it.
|
||||
// If item is in cache but hasn't received a response yet, sleep for a bit.
|
||||
// If item is not in cache, break out of loop and forward request to callback.
|
||||
if (RESPONSE_VALID(return_action)) {
|
||||
if (return_action == ACTION_RESPOND_ALLOW) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
|
||||
} else {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
|
||||
}
|
||||
return YES;
|
||||
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
|
||||
// TODO(rah): Look at a replacement for msleep(), maybe NSCondition
|
||||
usleep(5000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[self addToCache:vnode_id decision:ACTION_REQUEST_BINARY timeout:0];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (int)postAction:(santa_action_t)action forMessage:(santa_message_t)sm
|
||||
API_AVAILABLE(macos(10.15)) {
|
||||
es_respond_result_t ret;
|
||||
switch (action) {
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
if (sm.pid >= PID_MAX) {
|
||||
LOGE(@"Unable to watch compiler pid=%d >= pid_max=%d", sm.pid, PID_MAX);
|
||||
} else {
|
||||
LOGD(@"Watching compiler pid=%d path=%s", sm.pid, sm.path);
|
||||
self->_compilerPIDs[sm.pid].store(true);
|
||||
}
|
||||
// Allow the exec, but don't cache the decision so subsequent execs of the compiler get
|
||||
// marked appropriately.
|
||||
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
|
||||
ES_AUTH_RESULT_ALLOW, false);
|
||||
break;
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE:
|
||||
[self addToCache:sm.vnode_id decision:ACTION_RESPOND_ALLOW timeout:GetCurrentUptime()];
|
||||
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
|
||||
ES_AUTH_RESULT_ALLOW, true);
|
||||
break;
|
||||
case ACTION_RESPOND_DENY:
|
||||
case ACTION_RESPOND_TOOLONG:
|
||||
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
|
||||
ES_AUTH_RESULT_DENY, false);
|
||||
break;
|
||||
case ACTION_RESPOND_ACK:
|
||||
return ES_RESPOND_RESULT_SUCCESS;
|
||||
default:
|
||||
ret = ES_RESPOND_RESULT_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)addToCache:(santa_vnode_id_t)identifier
|
||||
decision:(santa_action_t)decision
|
||||
timeout:(uint64_t)microsecs {
|
||||
switch (decision) {
|
||||
case ACTION_REQUEST_BINARY:
|
||||
_decisionCache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
|
||||
break;
|
||||
case ACTION_RESPOND_ACK:
|
||||
_decisionCache->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
|
||||
((uint64_t)ACTION_REQUEST_BINARY << 56));
|
||||
break;
|
||||
case ACTION_RESPOND_ALLOW:
|
||||
case ACTION_RESPOND_ALLOW_COMPILER:
|
||||
case ACTION_RESPOND_DENY: {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
if (!_decisionCache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
|
||||
_decisionCache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: {
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
|
||||
_decisionCache->set(identifier, val, 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// TODO(rah): Look at a replacement for wakeup(), maybe NSCondition
|
||||
}
|
||||
|
||||
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly API_AVAILABLE(macos(10.15)) {
|
||||
_decisionCache->clear();
|
||||
if (!self.connectionEstablished) return YES; // if not connected, there's nothing to flush.
|
||||
return es_clear_cache(self.client) == ES_CLEAR_CACHE_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheCounts {
|
||||
return @[@(_decisionCache->count()), @(0)];
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)cacheBucketCount {
|
||||
// TODO: add this, maybe.
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
|
||||
auto result = ACTION_UNSET;
|
||||
uint64_t decision_time = 0;
|
||||
|
||||
uint64_t cache_val = _decisionCache->get(vnodeID);
|
||||
if (cache_val == 0) return result;
|
||||
|
||||
// Decision is stored in upper 8 bits, timestamp in remaining 56.
|
||||
result = (santa_action_t)(cache_val >> 56);
|
||||
decision_time = (cache_val & ~(0xFF00000000000000));
|
||||
|
||||
if (RESPONSE_VALID(result)) {
|
||||
if (result == ACTION_RESPOND_DENY) {
|
||||
auto expiry_time = decision_time + (500 * 100000); // kMaxCacheDenyTimeMilliseconds
|
||||
if (expiry_time < GetCurrentUptime()) {
|
||||
_decisionCache->remove(vnodeID);
|
||||
return ACTION_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeID {
|
||||
_decisionCache->remove(vnodeID);
|
||||
// TODO(rah): Look at a replacement for wakeup(), maybe NSCondition
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -17,5 +17,14 @@
|
||||
#include "Source/common/SNTKernelCommon.h"
|
||||
#include "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
|
||||
const pid_t PID_MAX = 99999;
|
||||
|
||||
@interface SNTEndpointSecurityManager : NSObject<SNTEventProvider>
|
||||
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file;
|
||||
|
||||
@property(readonly, nonatomic) es_client_t *client;
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,21 +16,17 @@
|
||||
|
||||
#include "Source/common/SNTPrefixTree.h"
|
||||
|
||||
#import "Source/common/SantaCache.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
#include <atomic>
|
||||
#include <bsm/libbsm.h>
|
||||
#include <libproc.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
|
||||
#define PID_MAX 99999
|
||||
|
||||
@interface SNTEndpointSecurityManager ()
|
||||
|
||||
@property(nonatomic) es_client_t *client;
|
||||
@property(nonatomic) SNTPrefixTree *prefixTree;
|
||||
@property(nonatomic, copy) void (^decisionCallback)(santa_message_t);
|
||||
@property(nonatomic, copy) void (^logCallback)(santa_message_t);
|
||||
@@ -83,7 +79,7 @@
|
||||
// If enabled, skip any action generated from another endpoint security client.
|
||||
if (m->process->is_es_client && config.ignoreOtherEndpointSecurityClients) {
|
||||
if (m->action_type == ES_ACTION_TYPE_AUTH) {
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
|
||||
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
|
||||
}
|
||||
if (self.selfPID != pid) {
|
||||
LOGD(@"Skipping event type: 0x%x from es_client pid: %d", m->event_type, pid);
|
||||
@@ -107,6 +103,9 @@
|
||||
// Ignore unmodified files
|
||||
if (!m->event.close.modified) return;
|
||||
|
||||
// Remove from decision cache in case this is invalidating a cached binary.
|
||||
[self removeCacheEntryForVnodeID:[self vnodeIDForFile:m->event.close.target]];
|
||||
|
||||
// Create a transitive rule if the file was modified by a running compiler
|
||||
if (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()) {
|
||||
santa_message_t sm = {};
|
||||
@@ -154,7 +153,7 @@
|
||||
// Update the set of running compiler PIDs
|
||||
if (pid && pid < PID_MAX) self->_compilerPIDs[pid].store(false);
|
||||
|
||||
// Skip the standard pipline and just log.
|
||||
// Skip the standard pipeline and just log.
|
||||
if (![config enableForkAndExitLogging]) return;
|
||||
santa_message_t sm = {};
|
||||
sm.action = ACTION_NOTIFY_EXIT;
|
||||
@@ -170,7 +169,7 @@
|
||||
return;
|
||||
}
|
||||
case ES_EVENT_TYPE_NOTIFY_FORK: {
|
||||
// Skip the standard pipline and just log.
|
||||
// Skip the standard pipeline and just log.
|
||||
if (![config enableForkAndExitLogging]) return;
|
||||
santa_message_t sm = {};
|
||||
sm.action = ACTION_NOTIFY_FORK;
|
||||
@@ -236,7 +235,7 @@
|
||||
switch (ret) {
|
||||
case ES_NEW_CLIENT_RESULT_SUCCESS:
|
||||
LOGI(@"Connected to EndpointSecurity");
|
||||
self.client = client;
|
||||
_client = client;
|
||||
return;
|
||||
case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
|
||||
LOGE(@"Unable to create EndpointSecurity client, not full-disk access permitted");
|
||||
@@ -251,6 +250,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)messageHandler:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
|
||||
santa_message_t sm = {};
|
||||
sm.es_message = (void *)m;
|
||||
@@ -261,6 +264,10 @@
|
||||
|
||||
switch (m->event_type) {
|
||||
case ES_EVENT_TYPE_AUTH_EXEC: {
|
||||
if ([self respondFromCache:m]) {
|
||||
return;
|
||||
}
|
||||
|
||||
sm.action = ACTION_REQUEST_BINARY;
|
||||
targetFile = m->event.exec.target->executable;
|
||||
targetProcess = m->event.exec.target;
|
||||
@@ -553,4 +560,11 @@
|
||||
return truncated;
|
||||
}
|
||||
|
||||
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file {
|
||||
return {
|
||||
.fsid = (uint64_t)file->stat.st_dev,
|
||||
.fileid = file->stat.st_ino,
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
#import "Source/santad/DataLayer/SNTEventTable.h"
|
||||
#import "Source/santad/EventProviders/SNTDriverManager.h"
|
||||
#import "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
|
||||
#import "Source/santad/EventProviders/SNTEventProvider.h"
|
||||
#import "Source/santad/Logs/SNTFileEventLog.h"
|
||||
@@ -59,8 +60,13 @@
|
||||
// Choose an event logger.
|
||||
// Locate and connect to driver / SystemExtension
|
||||
if ([configurator enableSystemExtension]) {
|
||||
LOGI(@"Using EndpointSecurity as event provider.");
|
||||
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
|
||||
if ([configurator enableSysxCache]) {
|
||||
LOGI(@"Using CachingEndpointSecurity as event provider.");
|
||||
_eventProvider = [[SNTCachingEndpointSecurityManager alloc] init];
|
||||
} else {
|
||||
LOGI(@"Using EndpointSecurity as event provider.");
|
||||
_eventProvider = [[SNTEndpointSecurityManager alloc] init];
|
||||
}
|
||||
} else {
|
||||
LOGI(@"Using Kauth as event provider.");
|
||||
_eventProvider = [[SNTDriverManager alloc] init];
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""The version for all Santa components."""
|
||||
|
||||
SANTA_VERSION = "1.16"
|
||||
SANTA_VERSION = "2021.1"
|
||||
|
||||
Reference in New Issue
Block a user