mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
* Initial structure for ES wrappers, enriched types, logging * Basic working ES and logging functionality * Add in oneTBB and thread-safe-lru deps * Added a bunch of enriched types * Auto-mute self when establishing ES client * Basic auth, tamper client. Syslog of all events. Basic compiler tracking. * Update copyright header blobs, convert some tabs to spaces * Auth result cache. Fix getting translocation path. * Added remaining cache methods * Add AuthResultCache to Recorder client. Cache now operates on es_file_t. * Hooked up SNTPrefixTree * Fix CompilerController for RENAME. Fix AllowList logging missing path. * Block loading Santa kext * Added device manager client * Properly log DiskAppear events * Fix build to adopt new adhoc build * Handle clearing cache on UNMOUNT events * Ignore other ES clients if configured * Remove SNTAllowlistInfo. Rename AllowList to Allowlist. Minor cleanup. * Recorder now logs asynchronously. Enricher now returns shared_ptrs. * Added File writer. Added timestamps to BasicStream serializer. * Skip calling stat in SNTFileInfo when path given by ES. * Fix build issue * Address draft PR feedback * santactl integrated, XPC works, fix file writer bug * Integrate syncservice. Start observing some config changes. * Add metrics service wrapper * Add metrics config observers and metrics interval reset. * Start better dependency control. Add Null logger support. * Added more deps * Added more deps * Fix issue where metric service wasn't starting * Add missing variant include * Fix missing parent proc name * Added googletest and new unit test macro * Started expanding AuthResultCacheTest * Properly mock EndpointSecurityAPI * Finished AuthResultCacheTest * bazelrc now builds all C++ as C++17. Added LoggerTest. * Add FileTest. Abstract some File constants to Logger. * Added Empty serializer test * Started work on BasicStringTest. Fixed some BasicString serialization bugs. * Added Unlink BasicString serialization test * Added some more tests. Commonized some test code * Finished BasicStringTest. Converted to XCTest. * Standardize esapi variable naming * Bubble up gTest expect failures to XCTest failures * AuthResultCacheTest now uses XCTest. Added common TestUtils.h * EmptyTest now uses XCTest. * FileTest now uses XCTest * LoggerTest now uses XCTest. Removed santa_unit_gtest bazel macro. * Added ClientTest * Add basic Enricher tests * Add MessageTest. Make more TestUtils. * Rename metrics to Metrics * Add MetricsTest. * Apply template pattern to Serializer * Add SNTDecisionCacheTest. * Add SNTCachedDecisionTest. * Testing with coveralls debug mode * Allow manual CI runs * Remove unused property * Started work on SNTEndpointSecurityClientTest. * WIP SNTEndpointSecurityClientTest, fix test run issue * Added more base ES client tests * Add more base ES client tests * Base ES client tests done. Added serializer utils/tests. Expanded basic string tests. * Add utils test to test suite * Add copy ctor. Add test output to bazel coverage. * Single thread bazel coverage * Updaload coverage file * Updaload coverage file * Old gen cov test * Restructure message handlers to enable better testability * Added enable tests for all ES clients * Made a single MockEndpointSecurityAPI class to share everywhere * Added most of SNTCompilerControllerTest * Cleanup SNTCompilerControllerTest * Started expanding Auth client test * Finished up the Authorizer tests * Move to using enum class for notify/auth instead of bool * WIP for tamper resistance test. ASAN issues. * Add OCMock patch to fix test issue on ARM Macs * Changed patches directory name to external_patches * Update WORKSPACE path * Finished up Tamper Resistance tests * Finished up Recorder tests. * Move SNTExecutionControllerTest to ObjC++ * Initial work to port SNTExecutionControllerTest * Finished porting SNTExecutionControllerTest. * Added SNTExecutionControllerTest to list of unit tests * Ported SNTEndpointSecurityDeviceManager. * Test cleanup, use MockESAPI expectation helpers * Verify SNTEndpointSecurityDeviceManager expectations differently * Test cleanup, omit gTest param list where unused * Log message cleanup * Rename SNTApplicationTest to santad_test.mm * Finished porting santad_test, formerly SNTApplicationTest * Fix SNTEndpointSecurityDeviceManager issues * Pulled in missed fixes. Updated tests. * Renamed lowercase filenames to match rest of codebase * Fix non-static dispatch_once_t, and noisy watching compiler log message * WIP Started process of removing components no longer used * WIP Continued process of removing components no longer used * BUILD file cleanup. Proto warning. Removed unused global * Rename SNTEventProvider to SNTEndpointSecurityEventHandler * Rename SNTEndpointSecurityEventHandler protocol * Remove EnableSysxCache option. Remove --quick flag used during dev. * Ran testing/fix.sh * Addmissing param to fix.sh that was omitting .mm files. * clang-format * Fix linter: find cmd missing .mm ext, git grep exclude patch files. * Use MakeESProcess default params in tests * Move variables to camelCase in objc classes * More case changes * Sanitize strings * Change dispatch queue priorities and standardize daemon queue naming * Exclude patch files in markdown check * Ensure string log messages end with newline * Fix BasicStringTest * Disable clang-format in code producing different results in local/remote versions * Moved to using date ranges in copyright notices as per current guidelines * Update Source/common/SNTConfigurator.h Suggestion adding whitespace in comment to fix clang-format mangling Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com> * Removed santa_panic macro used in one place * Updated comment about ES cachability * Pin oneTBB to specific commit * Address outstanding WORKSPACE 'canonical reproducible form' messages * Use string append instead of ostringstream due to benchmark results * Remove use of freind classes in EnrichedTypes.h * Added SNTKVOManager, removed observers from SNTConfigurator. * Fixed SNTEndpointSecurityRecorderTest class name * Reduce usage of the auto keyword * Each SNTKVOManager instance now adds its own observer * Replaced more auto keywords with real types. * Remove leftover code coverage debugging from ci.yml * Updated comment * Memoize SNTFileInfo sha256. Reduce some cache sizes. * Fix issue checking for translocated paths * Use more performant NSURL creation method * Fix lint issue * Address PR feedback * Use an array literal for kvo objects * Fix some clang tidy and import issues * Replace third party LRU cache with SantaCache for now * Fix clang tidy issues * Address PR feedback * Fix comment typo Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com> * Added todo for when we adopt macOS 13 Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com> Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>
369 lines
11 KiB
C++
369 lines
11 KiB
C++
/// Copyright 2016-2022 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__SANTACACHE_H
|
|
#define SANTA__SANTA_DRIVER__SANTACACHE_H
|
|
|
|
#include <libkern/OSAtomic.h>
|
|
#include <libkern/OSTypes.h>
|
|
#include <os/log.h>
|
|
#include <stdint.h>
|
|
#include <sys/cdefs.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "Source/common/SNTCommon.h"
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
|
/**
|
|
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 (uint64_t)t * 11400714819323198549UL;
|
|
};
|
|
|
|
/**
|
|
A somewhat simple, concurrent linked-list hash table intended for use in IOKit
|
|
kernel extensions.
|
|
|
|
The type used for keys must overload the == operator and a specialization of
|
|
SantaCacheHasher must exist for it.
|
|
|
|
Enforces a maximum size by clearing all entries if a new value
|
|
is added that would go over the maximum size declared at creation.
|
|
|
|
The number of buckets is calculated as `maximum_size` / `per_bucket`
|
|
rounded up to the next power of 2. Locking is done per-bucket.
|
|
*/
|
|
template <typename KeyT, typename ValueT>
|
|
class SantaCache {
|
|
public:
|
|
/**
|
|
Initialize a newly created cache.
|
|
|
|
@param maximum_size The maximum number of entries in this cache. Once this
|
|
number is reached all the entries will be purged.
|
|
@param per_bucket The target number of entries in each bucket when cache is
|
|
full. A higher number will result in better performance but higher memory
|
|
usage. Cannot be higher than 64 to try and ensure buckets don't overflow.
|
|
*/
|
|
SantaCache(uint64_t maximum_size = 10000, uint8_t per_bucket = 5) {
|
|
if (unlikely(per_bucket > maximum_size)) per_bucket = (uint8_t)maximum_size;
|
|
if (unlikely(per_bucket < 1)) per_bucket = 1;
|
|
if (unlikely(per_bucket > 64)) per_bucket = 64;
|
|
max_size_ = maximum_size;
|
|
bucket_count_ =
|
|
(1 << (32 -
|
|
__builtin_clz((((uint32_t)max_size_ / per_bucket) - 1) ?: 1)));
|
|
if (unlikely(bucket_count_ > UINT32_MAX)) bucket_count_ = UINT32_MAX;
|
|
buckets_ = (struct bucket *)malloc(bucket_count_ * sizeof(struct bucket));
|
|
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
|
}
|
|
|
|
/**
|
|
Clear and free memory
|
|
*/
|
|
~SantaCache() {
|
|
clear();
|
|
free(buckets_);
|
|
}
|
|
|
|
/**
|
|
Get an element from the cache. Returns zero_ if item doesn't exist.
|
|
*/
|
|
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 val;
|
|
}
|
|
entry = entry->next;
|
|
}
|
|
unlock(bucket);
|
|
return zero_;
|
|
}
|
|
|
|
/**
|
|
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.
|
|
|
|
@return true if the value was set.
|
|
*/
|
|
bool set(const KeyT &key, const ValueT &value) {
|
|
return set(key, value, {}, false);
|
|
}
|
|
|
|
/**
|
|
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 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.
|
|
|
|
@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(const KeyT &key) { set(key, zero_); }
|
|
|
|
/**
|
|
Remove all entries and free bucket memory.
|
|
*/
|
|
void clear() {
|
|
for (uint32_t i = 0; i < bucket_count_; ++i) {
|
|
struct bucket *bucket = &buckets_[i];
|
|
// We grab the lock so nothing can use this bucket while we're erasing it
|
|
// and never release it. It'll be 'released' when the bzero call happens
|
|
// at the end of this function.
|
|
lock(bucket);
|
|
|
|
// Free the bucket's entries, if there are any.
|
|
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
|
|
while (entry != nullptr) {
|
|
struct entry *next_entry = entry->next;
|
|
free(entry);
|
|
entry = next_entry;
|
|
}
|
|
}
|
|
|
|
// Reset cache count, no atomicity needed as we hold all the bucket locks.
|
|
count_ = 0;
|
|
|
|
// This resets all of the bucket counts and locks. Releasing the locks for
|
|
// each bucket isn't really atomic here but each bucket will be zero'd
|
|
// before the lock is released as the lock is the last thing in a bucket.
|
|
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
|
|
}
|
|
|
|
/**
|
|
Return number of entries currently in cache.
|
|
*/
|
|
inline uint64_t count() const { 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 = (uint16_t)(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 {
|
|
KeyT key;
|
|
ValueT value;
|
|
struct entry *next;
|
|
};
|
|
|
|
struct bucket {
|
|
// The least significant bit of this pointer is always 0 (due to alignment),
|
|
// so we utilize that bit as the lock for the bucket.
|
|
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);
|
|
}
|
|
free(entry);
|
|
OSAtomicDecrement64((volatile int64_t *)&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 *)malloc(sizeof(struct entry));
|
|
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);
|
|
OSAtomicIncrement64((volatile int64_t *)&count_);
|
|
|
|
unlock(bucket);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Lock a bucket. Spins until the lock is acquired.
|
|
*/
|
|
inline void lock(struct bucket *bucket) const {
|
|
while (OSAtomicTestAndSet(7, (volatile uint8_t *)&bucket->head))
|
|
;
|
|
}
|
|
|
|
/**
|
|
Unlock a bucket. Panics if the lock wasn't locked.
|
|
*/
|
|
inline void unlock(struct bucket *bucket) const {
|
|
if (unlikely(OSAtomicTestAndClear(7, (volatile uint8_t *)&bucket->head) ==
|
|
0)) {
|
|
os_log_error(OS_LOG_DEFAULT,
|
|
"SantaCache::unlock(): Tried to unlock an unlocked lock");
|
|
abort();
|
|
}
|
|
}
|
|
|
|
uint64_t count_ = 0;
|
|
|
|
uint64_t max_size_;
|
|
uint32_t bucket_count_;
|
|
|
|
struct bucket *buckets_;
|
|
|
|
/**
|
|
Holder for a 'zero' entry for the current type
|
|
*/
|
|
const ValueT zero_ = {};
|
|
|
|
/**
|
|
Special bucket used when automatically clearing due to size
|
|
to prevent two threads trying to clear at the same time and
|
|
getting stuck.
|
|
*/
|
|
struct bucket clear_bucket_ = {};
|
|
|
|
/**
|
|
Hash a key to determine which bucket it belongs in.
|
|
*/
|
|
inline uint64_t hash(KeyT input) const {
|
|
return SantaCacheHasher<KeyT>(input) % bucket_count_;
|
|
}
|
|
};
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H
|