Skip to content

Commit

Permalink
santa-driver: Switch to a struct for vnode IDs, holding both the file… (
Browse files Browse the repository at this point in the history
#276)

santa-driver: Switch to a struct for vnode IDs, holding both the filesystem ID and vnode ID.

Also drop the separate caches for root/non-root as this doesn't offer any benefit anymore.
  • Loading branch information
russellhancox authored Jun 5, 2018
1 parent 9595f80 commit 15fa53d
Show file tree
Hide file tree
Showing 18 changed files with 164 additions and 187 deletions.
2 changes: 2 additions & 0 deletions Santa.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INSTALL_PATH = "";
OTHER_CODE_SIGN_FLAGS = "";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-fcxx-modules",
Expand Down Expand Up @@ -1618,6 +1619,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INSTALL_PATH = "";
OTHER_CODE_SIGN_FLAGS = "";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-fcxx-modules",
Expand Down
19 changes: 18 additions & 1 deletion Source/common/SNTKernelCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,27 @@ typedef enum {
#define RESPONSE_VALID(x) \
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY)

// 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;
Expand Down
4 changes: 2 additions & 2 deletions Source/common/SNTXPCControlInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
///
/// Kernel ops
///
- (void)cacheCounts:(void (^)(uint64_t rootCache, uint64_t nonRootCache))reply;
- (void)cacheCounts:(void (^)(uint64_t count))reply;
- (void)flushCache:(void (^)(BOOL))reply;
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply;
- (void)cacheBucketCount:(void (^)(NSArray *))reply;
- (void)checkCacheForVnodeID:(santa_vnode_id_t)vnodeID withReply:(void (^)(santa_action_t))reply;

///
/// Database ops
Expand Down
2 changes: 1 addition & 1 deletion Source/santa-driver/SantaCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ template<typename KeyT, typename ValueT> class SantaCache {
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
if (entry->key != 0) ++count;
if (entry->value != zero_) ++count;
entry = entry->next;
}
unlock(bucket);
Expand Down
91 changes: 30 additions & 61 deletions Source/santa-driver/SantaDecisionManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);

#pragma mark Object Lifecycle

template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& s) {
return (SantaCacheHasher<uint64_t>(s.fsid) << 1) ^ SantaCacheHasher<uint64_t>(s.fileid);
}

bool SantaDecisionManager::init() {
if (!super::init()) return false;

Expand All @@ -29,9 +33,8 @@ bool SantaDecisionManager::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_);

root_decision_cache_ = new SantaCache<uint64_t, uint64_t>(5000, 2);
non_root_decision_cache_ = new SantaCache<uint64_t, uint64_t>(500, 2);
vnode_pid_map_ = new SantaCache<uint64_t, uint64_t>(2000, 5);
decision_cache_ = new SantaCache<santa_vnode_id_t, uint64_t>(10000, 2);
vnode_pid_map_ = new SantaCache<santa_vnode_id_t, uint64_t>(2000, 5);

decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
Expand All @@ -42,14 +45,12 @@ bool SantaDecisionManager::init() {
if (!log_dataqueue_) return kIOReturnNoMemory;

client_pid_ = 0;
root_fsid_ = 0;

return true;
}

void SantaDecisionManager::free() {
delete root_decision_cache_;
delete non_root_decision_cache_;
delete decision_cache_;
delete vnode_pid_map_;

if (decision_dataqueue_lock_) {
Expand Down Expand Up @@ -90,17 +91,6 @@ void SantaDecisionManager::ConnectClient(pid_t pid) {

client_pid_ = pid;

// Determine root fsid
vfs_context_t ctx = vfs_context_create(NULL);
if (ctx) {
vnode_t root = vfs_rootvnode();
if (root) {
root_fsid_ = GetVnodeIDForVnode(ctx, root) >> 32;
vnode_put(root);
}
vfs_context_rele(ctx);
}

// Any decisions made while the daemon wasn't
// connected should be cleared
ClearCache();
Expand Down Expand Up @@ -207,79 +197,58 @@ kern_return_t SantaDecisionManager::StopListener() {

#pragma mark Cache Management

/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<uint64_t, uint64_t>* SantaDecisionManager::CacheForIdentifier(
const uint64_t identifier) {
return (identifier >> 32 == root_fsid_) ?
root_decision_cache_ : non_root_decision_cache_;
}

void SantaDecisionManager::AddToCache(
uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
auto decision_cache = CacheForIdentifier(identifier);

santa_vnode_id_t identifier, santa_action_t decision, uint64_t microsecs) {
switch (decision) {
case ACTION_REQUEST_BINARY:
decision_cache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
decision_cache_->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
break;
case ACTION_RESPOND_ACK:
decision_cache->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
decision_cache_->set(identifier, (uint64_t)ACTION_RESPOND_ACK << 56,
((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
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 (!decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
if (!decision_cache_->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache_->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
break;
}
default:
break;
}

wakeup((void *)identifier);
wakeup((void *)identifier.unsafe_simple_id());
}

void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
CacheForIdentifier(identifier)->remove(identifier);
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
void SantaDecisionManager::RemoveFromCache(santa_vnode_id_t identifier) {
if (unlikely(identifier.fsid == 0 && identifier.fileid == 0)) return;
decision_cache_->remove(identifier);
wakeup((void *)identifier.unsafe_simple_id());
}

uint64_t SantaDecisionManager::RootCacheCount() const {
return root_decision_cache_->count();
uint64_t SantaDecisionManager::CacheCount() const {
return decision_cache_->count();
}

uint64_t SantaDecisionManager::NonRootCacheCount() const {
return non_root_decision_cache_->count();
}

void SantaDecisionManager::ClearCache(bool non_root_only) {
if (!non_root_only) root_decision_cache_->clear();
non_root_decision_cache_->clear();
void SantaDecisionManager::ClearCache() {
decision_cache_->clear();
}

void SantaDecisionManager::CacheBucketCount(
uint16_t *per_bucket_counts, uint16_t *array_size, uint64_t *start_bucket) {
non_root_decision_cache_->bucket_counts(per_bucket_counts, array_size, start_bucket);
decision_cache_->bucket_counts(per_bucket_counts, array_size, start_bucket);
}

#pragma mark Decision Fetching

santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
santa_action_t SantaDecisionManager::GetFromCache(santa_vnode_id_t identifier) {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;

auto decision_cache = CacheForIdentifier(identifier);

uint64_t cache_val = decision_cache->get(identifier);
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.
Expand All @@ -290,7 +259,7 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache->remove(identifier);
decision_cache_->remove(identifier);
return ACTION_UNSET;
}
}
Expand All @@ -300,7 +269,7 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
}

santa_action_t SantaDecisionManager::GetFromDaemon(
santa_message_t *message, uint64_t identifier) {
santa_message_t *message, santa_vnode_id_t identifier) {
auto return_action = ACTION_UNSET;

#ifdef DEBUG
Expand Down Expand Up @@ -328,7 +297,7 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
// request, indicated with ACTION_RESPOND_ACK.
auto cache_check_count = 0;
do {
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
msleep((void *)message->vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} while (ClientConnected() &&
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
Expand All @@ -355,7 +324,7 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id) {
const santa_vnode_id_t vnode_id) {
while (true) {
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;

Expand All @@ -370,7 +339,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
// until AddToCache is called, indicating a response has arrived.
msleep((void *)vnode_id, NULL, 0, "", &ts_);
msleep((void *)vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
} else {
break;
}
Expand Down Expand Up @@ -450,7 +419,7 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
int *errno) {
// Get ID for the vnode
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
if (!vnode_id) return KAUTH_RESULT_DEFER;
if (vnode_id.fsid == 0 && vnode_id.fileid == 0) return KAUTH_RESULT_DEFER;

// Fetch decision
auto returnedAction = FetchDecision(cred, vp, vnode_id);
Expand Down
46 changes: 16 additions & 30 deletions Source/santa-driver/SantaDecisionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,28 +86,24 @@ class SantaDecisionManager : public OSObject {
kern_return_t StopListener();

/// Adds a decision to the cache, with a timestamp.
void AddToCache(uint64_t identifier,
void AddToCache(santa_vnode_id_t identifier,
const santa_action_t decision,
const uint64_t microsecs = GetCurrentUptime());

/**
Fetches a response from the cache, first checking to see if the entry
has expired.
*/
santa_action_t GetFromCache(uint64_t identifier);
santa_action_t GetFromCache(santa_vnode_id_t identifier);

/// Checks to see if a given identifier is in the cache and removes it.
void RemoveFromCache(uint64_t identifier);
void RemoveFromCache(santa_vnode_id_t identifier);

/// Returns the number of entries in the cache.
uint64_t RootCacheCount() const;
uint64_t NonRootCacheCount() const;
uint64_t CacheCount() const;

/**
Clears the cache(s). If non_root_only is true, only the non-root cache
is cleared.
*/
void ClearCache(bool non_root_only = false);
/// Clears the cache.
void ClearCache();


/**
Expand All @@ -134,15 +130,18 @@ class SantaDecisionManager : public OSObject {
@param ctx The VFS context to use.
@param vp The Vnode to get the ID for
@return uint64_t The Vnode ID as a 64-bit unsigned int.
@return santa_vnode_id_t The Vnode ID.
*/
static inline uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) {
static inline santa_vnode_id_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, ctx);
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
return {
.fsid = vap.va_fsid,
.fileid = vap.va_fileid
};
}

/**
Expand Down Expand Up @@ -212,7 +211,7 @@ class SantaDecisionManager : public OSObject {
@param identifier The vnode ID string for this request
@return santa_action_t The response for this request
*/
santa_action_t GetFromDaemon(santa_message_t *message, uint64_t identifier);
santa_action_t GetFromDaemon(santa_message_t *message, santa_vnode_id_t identifier);

/**
Fetches an execution decision for a file, first using the cache and then
Expand All @@ -226,7 +225,7 @@ class SantaDecisionManager : public OSObject {
@return santa_action_t The response for this request
*/
santa_action_t FetchDecision(
const kauth_cred_t cred, const vnode_t vp, const uint64_t vnode_id);
const kauth_cred_t cred, const vnode_t vp, const santa_vnode_id_t vnode_id);

/**
Posts the requested message to the decision data queue.
Expand Down Expand Up @@ -280,21 +279,8 @@ class SantaDecisionManager : public OSObject {
return (uint64_t)((sec * 1000000) + usec);
}

SantaCache<uint64_t, uint64_t> *root_decision_cache_;
SantaCache<uint64_t, uint64_t> *non_root_decision_cache_;
SantaCache<uint64_t, uint64_t> *vnode_pid_map_;

/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<uint64_t, uint64_t>* CacheForIdentifier(const uint64_t identifier);

// This is the file system ID of the root filesystem,
// used to determine which cache to use for requests
uint32_t root_fsid_;
SantaCache<santa_vnode_id_t, uint64_t> *decision_cache_;
SantaCache<santa_vnode_id_t, uint64_t> *vnode_pid_map_;

lck_grp_t *sdm_lock_grp_;
lck_grp_attr_t *sdm_lock_grp_attr_;
Expand Down
Loading

0 comments on commit 15fa53d

Please sign in to comment.