add in-kernel filemod prefix filter (#313)

* add in-kernel filemod prefix filter

* byte lookup

* added pruning and tests

* clang-format

* add TODO

* don't need seen

* review updates

* reset filter on client connect

* DisconnectClient: reset filter
AddPrefix: when a branch is needed create the whole branch immediately

* don't use strlen in HasPrefix
use strnlen in AddPrefix
up max nodes to 1024

* use new[] and delete[] for the prune "stack"
revert clang-format changes to kernel tests
remove reset node count

* words

* count not size
This commit is contained in:
Tom Burgin
2018-11-08 15:37:30 -05:00
committed by GitHub
parent db0cd861d6
commit 2695355dd2
14 changed files with 543 additions and 2 deletions

View File

@@ -154,6 +154,7 @@
B352A545B76783D568A6D0C5 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90E9D568200AB9B642E06272 /* libPods-Santa.a */; };
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */; };
C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; };
C719726B218A419B00979487 /* SantaPrefixTree.cc in Sources */ = {isa = PBXBuildFile; fileRef = C719726A218A419B00979487 /* SantaPrefixTree.cc */; };
C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */; };
C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; };
C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
@@ -433,6 +434,8 @@
90E9D568200AB9B642E06272 /* libPods-Santa.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Santa.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A6A91785C40257CC156B4F05 /* Pods-Santa.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.release.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.release.xcconfig"; sourceTree = "<group>"; };
C11A10A5D6E112788769CF70 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; };
C7197268218A3AF600979487 /* SantaPrefixTree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SantaPrefixTree.h; sourceTree = "<group>"; };
C719726A218A419B00979487 /* SantaPrefixTree.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SantaPrefixTree.cc; sourceTree = "<group>"; };
C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFileInfoTest.m; sourceTree = "<group>"; };
C748E8A1206964DE006CFD1B /* SNTEventLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTEventLog.h; sourceTree = "<group>"; };
C748E8A2206964DE006CFD1B /* SNTEventLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTEventLog.m; sourceTree = "<group>"; };
@@ -721,6 +724,8 @@
0D9A7F311759144800035EB5 /* SantaDriver.cc */,
0D9A7F361759148E00035EB5 /* SantaDriverClient.h */,
0D9A7F351759148E00035EB5 /* SantaDriverClient.cc */,
C7197268218A3AF600979487 /* SantaPrefixTree.h */,
C719726A218A419B00979487 /* SantaPrefixTree.cc */,
0DA36C1F199EA46600A129D6 /* Resources */,
);
path = "santa-driver";
@@ -1467,6 +1472,7 @@
buildActionMask = 2147483647;
files = (
0D9A7F331759144800035EB5 /* SantaDriver.cc in Sources */,
C719726B218A419B00979487 /* SantaPrefixTree.cc in Sources */,
0D9A7F371759148E00035EB5 /* SantaDriverClient.cc in Sources */,
0D4644C5182AF81700098690 /* SantaDecisionManager.cc in Sources */,
);

View File

@@ -72,6 +72,57 @@
///
@property(readonly, nonatomic) NSRegularExpression *fileChangesRegex;
///
/// A list of ignore prefixes which are checked in-kernel.
/// This is more performant than FileChangesRegex when ignoring whole directory trees.
///
/// 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.
///
/// By default "/." and "/dev/" are added.
///
/// 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.
///
/// "/private/tmp/"
/// "/private/var/"
/// "/private/new/"
///
/// -> [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
/// If this flag is set to NO, 32-bit binaries that are missing

View File

@@ -68,6 +68,7 @@ static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters";
static NSString *const kEventLogType = @"EventLogType";
static NSString *const kEventLogPath = @"EventLogPath";
@@ -93,6 +94,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
Class date = [NSDate class];
Class string = [NSString class];
Class data = [NSData class];
Class array = [NSArray class];
_syncServerKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveWhitelistingKey : number,
@@ -106,6 +108,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kClientModeKey : number,
kEnableTransitiveWhitelistingKey : number,
kFileChangesRegexKey : re,
kFileChangesPrefixFiltersKey : array,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kEnablePageZeroProtectionKey : number,
@@ -198,6 +201,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFileChangesPrefixFiltersKey {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
return [self configStateSet];
}
@@ -350,6 +357,17 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return self.configState[kFileChangesRegexKey];
}
- (NSArray *)fileChangesPrefixFilters {
NSArray *filters = self.configState[kFileChangesPrefixFiltersKey];
for (id filter in filters) {
if (![filter isKindOfClass:[NSString class]]) {
LOGE(@"Ignoring FileChangesPrefixFilters: array contains a non-string %@", filter);
return nil;
}
}
return filters;
}
- (NSURL *)syncBaseURL {
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];

View File

@@ -41,6 +41,8 @@ enum SantaDriverMethods {
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.

View File

@@ -57,6 +57,9 @@ bool SantaDecisionManager::init() {
client_pid_ = 0;
root_fsid_ = 0;
// Setup file modification prefix filter.
filemod_prefix_filter_ = new SantaPrefixTree();
return true;
}
@@ -95,6 +98,8 @@ void SantaDecisionManager::free() {
OSSafeReleaseNULL(decision_dataqueue_);
OSSafeReleaseNULL(log_dataqueue_);
delete filemod_prefix_filter_;
super::free();
}
@@ -150,6 +155,10 @@ void SantaDecisionManager::DisconnectClient(bool itDied, pid_t pid) {
kMaxLogQueueEvents, sizeof(santa_message_t));
lck_mtx_unlock(log_dataqueue_lock_);
}
// Reset the filter.
// On startup the daemon will add prefixes as needed.
FilemodPrefixFilterReset();
}
bool SantaDecisionManager::ClientConnected() const {
@@ -691,7 +700,7 @@ void SantaDecisionManager::FileOpCallback(
// Filter out modifications to locations that are definitely
// not useful or made by santad.
if (!strprefix(path, "/.") && !strprefix(path, "/dev")) {
if (!filemod_prefix_filter_->HasPrefix(path)) {
auto message = NewMessage(nullptr);
strlcpy(message->path, path, sizeof(message->path));
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));

View File

@@ -19,12 +19,12 @@
#include <IOKit/IOLib.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOSharedDataQueue.h>
#include <libkern/c++/OSDictionary.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "SantaCache.h"
#include "SantaPrefixTree.h"
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
@@ -164,6 +164,20 @@ class SantaDecisionManager : public OSObject {
*/
bool IsCompilerProcess(pid_t pid);
/**
Add a file modification prefix filter.
*/
inline IOReturn FilemodPrefixFilterAdd(const char *prefix, uint64_t *node_count = nullptr) {
return filemod_prefix_filter_->AddPrefix(prefix, node_count);
}
/**
Reset the file modification prefix filter tree.
*/
inline void FilemodPrefixFilterReset() {
filemod_prefix_filter_->Reset();
}
/**
Fetches the vnode_id for a given vnode.
@@ -333,6 +347,8 @@ class SantaDecisionManager : public OSObject {
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;
SantaCache<pid_t, pid_t> *compiler_pid_set_;
SantaPrefixTree *filemod_prefix_filter_;
/**
Return the correct cache for a given identifier.

View File

@@ -248,6 +248,24 @@ IOReturn SantaDriverClient::cache_bucket_count(
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::filemod_prefix_filter_add(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const char *prefix = reinterpret_cast<const char *>(arguments->structureInput);
return me->decisionManager->FilemodPrefixFilterAdd(prefix, arguments->scalarOutput);
}
IOReturn SantaDriverClient::filemod_prefix_filter_reset(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
me->decisionManager->FilemodPrefixFilterReset();
return kIOReturnSuccess;
}
#pragma mark Method Resolution
IOReturn SantaDriverClient::externalMethod(
@@ -271,6 +289,8 @@ IOReturn SantaDriverClient::externalMethod(
{ &SantaDriverClient::check_cache, 0, sizeof(santa_vnode_id_t), 1, 0 },
{ &SantaDriverClient::cache_bucket_count, 0, sizeof(santa_bucket_count_t),
0, sizeof(santa_bucket_count_t) },
{ &SantaDriverClient::filemod_prefix_filter_add, 0, sizeof(const char[MAXPATHLEN]), 1, 0 },
{ &SantaDriverClient::filemod_prefix_filter_reset, 0, 0, 0, 0 },
};
if (selector > static_cast<UInt32>(kSantaUserClientNMethods)) {

View File

@@ -121,6 +121,14 @@ class com_google_SantaDriverClient : public IOUserClient {
static IOReturn cache_bucket_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to add filemod prefix filters at daemon startup.
static IOReturn filemod_prefix_filter_add(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to reset the prefix filter tree.
static IOReturn filemod_prefix_filter_reset(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
private:
com_google_SantaDriver *myProvider;
SantaDecisionManager *decisionManager;

View File

@@ -0,0 +1,210 @@
/// 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 "SantaPrefixTree.h"
#include "SNTLogging.h"
SantaPrefixTree::SantaPrefixTree() {
root_ = new SantaPrefixNode();
node_count_ = 0;
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_);
}
IOReturn SantaPrefixTree::AddPrefix(const char *prefix, uint64_t *node_count) {
// 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, kMaxNodes);
// 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)) > kMaxNodes) {
LOGE("Prefix tree is full, can not add: %s", prefix);
if (node_count) *node_count = node_count_;
lck_rw_unlock_exclusive(spt_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_);
return kIOReturnSuccess;
}
bool SantaPrefixTree::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 SantaPrefixTree::Reset() {
lck_rw_lock_exclusive(spt_lock_);
PruneNode(root_);
root_ = new SantaPrefixNode();
node_count_ = 0;
lck_rw_unlock_exclusive(spt_lock_);
}
void SantaPrefixTree::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;
}
SantaPrefixTree::~SantaPrefixTree() {
lck_rw_lock_exclusive(spt_lock_);
PruneNode(root_);
root_ = nullptr;
lck_rw_unlock_exclusive(spt_lock_);
if (spt_lock_) {
lck_rw_free(spt_lock_, spt_lock_grp_);
spt_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;
}
}

View File

@@ -0,0 +1,85 @@
/// 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 <libkern/locks.h>
#include <sys/param.h>
///
/// SantaPrefixTree is a simple prefix tree implementation.
/// Operations are thread safe.
///
class SantaPrefixTree {
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();
SantaPrefixTree();
~SantaPrefixTree();
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 kMaxNodes = MAXPATHLEN;
uint32_t node_count_;
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_;
};
#endif /* SANTA__SANTA_DRIVER__SANTAPREFIXTREE_H */

View File

@@ -84,6 +84,13 @@
break;
}
// The filter is reset when santad disconnects from the driver.
// Add the default filters.
[_driverManager fileModificationPrefixFilterAdd:@[ @"/.", @"/dev/" ]];
// TODO(bur): Add KVO handling for fileChangesPrefixFilters.
[_driverManager fileModificationPrefixFilterAdd:[configurator fileChangesPrefixFilters]];
self.notQueue = [[SNTNotificationQueue alloc] init];
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];

View File

@@ -68,7 +68,9 @@
///
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID;
///
/// Returns whether the connection to the driver has been established.
///
@property(readonly) BOOL connectionEstablished;
///
@@ -76,4 +78,14 @@
///
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeId;
///
/// Adds in-kernel prefix filters for file modification logs.
///
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters;
///
/// Resets the filter.
///
- (void)fileModificationPrefixFilterReset;
@end

View File

@@ -278,4 +278,38 @@ static void driverAppearedHandler(void *info, io_iterator_t iterator) {
return a;
}
- (void)fileModificationPrefixFilterAdd:(NSArray *)filters {
uint64_t n = 0;
uint32_t n_len = 1;
for (NSString *filter in filters) {
char buffer[MAXPATHLEN];
if (![filter getFileSystemRepresentation:buffer maxLength:MAXPATHLEN]) {
LOGE(@"Invalid filemod prefix filter: %@", filter);
continue;
}
kern_return_t ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd,
NULL, 0, buffer, sizeof(const char[MAXPATHLEN]),
&n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess) {
LOGE(@"Failed to add prefix filter: %s error: 0x%x", buffer, ret);
// If the tree is full, stop.
// TODO(bur): Maybe continue here with some smarts around the size? Shorter, not yet iterated
// prefixes (that may fit) are ignored. Seems worth it not to spam the driver.
if (ret == kIOReturnNoResources) {
LOGE(@"Prefix filter tree is full!");
return;
}
}
}
}
- (void)fileModificationPrefixFilterReset {
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset,
NULL, 0, NULL, NULL);
}
@end

View File

@@ -702,6 +702,68 @@
TPASS();
}
- (void)testFilemodPrefixFilter {
TSTART("Testing filemod prefix filter");
NSString *filter =
@"Albert Einstein, in his theory of special relativity, determined that the laws of physics "
@"are the same for all non-accelerating observers, and he showed that the speed of light "
@"within a vacuum is the same no matter the speed at which an observer travels.";
// Create a buffer that has 1024 bytes of characters, non-terminating.
char buffer[MAXPATHLEN + 1]; // +1 is for the null byte from strlcpy(). It falls off the ledge.
for (int i = 0; i < 4; ++i) {
if (![filter getFileSystemRepresentation:buffer + (i * filter.length) maxLength:MAXPATHLEN]) {
TFAILINFO("Invalid filemod prefix filter: %s", filter.UTF8String);
}
}
strlcpy(&buffer[1016], "E = mc²", 9); // Fill in the last 8 bytes.
uint64_t n = 0;
uint32_t n_len = 1;
// The filter should currently be empty. It is reset when the client disconnects.
// Fill up the 1024 node capacity.
kern_return_t ret =
IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0, buffer,
sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess || n != 1024) {
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
}
// Make sure it will enforce capacity.
const char *too_much = "B";
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
too_much, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnNoResources || n != 1024) {
TFAILINFO("Failed enforce capacity: got %llu nodes expected 1024", n);
}
// Make sure it will prune.
const char *ignore_it_all = "A";
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
ignore_it_all, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
// Expect 1 "A" node.
if (ret != kIOReturnSuccess || n != 1) {
TFAILINFO("Failed to prune the prefix filter: got %llu nodes expected 1", n);
}
// Reset.
IOConnectCallScalarMethod(self.connection, kSantaUserClientFilemodPrefixFilterReset, NULL, 0,
NULL, NULL);
// And fill it back up again.
ret = IOConnectCallMethod(self.connection, kSantaUserClientFilemodPrefixFilterAdd, NULL, 0,
buffer, sizeof(const char[MAXPATHLEN]), &n, &n_len, NULL, NULL);
if (ret != kIOReturnSuccess || n != 1024) {
TFAILINFO("Failed to fill the prefix filter: got %llu nodes expected 1024", n);
}
TPASS();
}
#pragma mark - Main
- (void)unloadDaemon {
@@ -777,6 +839,7 @@
[self testLargeBinary];
[self testPendingTransitiveRules];
[self testNoTransitiveRules];
[self testFilemodPrefixFilter];
printf("\n-> Performance tests:\n");
[self testCachePerformance];