diff --git a/Santa.xcodeproj/project.pbxproj b/Santa.xcodeproj/project.pbxproj index efce68d9f..8d0d36133 100644 --- a/Santa.xcodeproj/project.pbxproj +++ b/Santa.xcodeproj/project.pbxproj @@ -568,12 +568,12 @@ 0D91BCB9174E8A7E00131A7D /* santa-driver */ = { isa = PBXGroup; children = ( - 0D4644C4182AF81700098690 /* SantaDecisionManager.h */, - 0D4644C3182AF81700098690 /* SantaDecisionManager.cc */, 0D9A7F321759144800035EB5 /* SantaDriver.h */, 0D9A7F311759144800035EB5 /* SantaDriver.cc */, 0D9A7F361759148E00035EB5 /* SantaDriverClient.h */, 0D9A7F351759148E00035EB5 /* SantaDriverClient.cc */, + 0D4644C4182AF81700098690 /* SantaDecisionManager.h */, + 0D4644C3182AF81700098690 /* SantaDecisionManager.cc */, 0D7A7AF2174FCF4C00B77646 /* SantaMessage.h */, 0D7A7AF1174FCF4C00B77646 /* SantaMessage.cc */, 0DA36C1F199EA46600A129D6 /* Resources */, diff --git a/Source/common/SNTKernelCommon.h b/Source/common/SNTKernelCommon.h index f8f5be921..d01dee829 100644 --- a/Source/common/SNTKernelCommon.h +++ b/Source/common/SNTKernelCommon.h @@ -30,7 +30,6 @@ // List of methods supported by the driver. enum SantaDriverMethods { kSantaUserClientOpen, - kSantaUserClientClose, kSantaUserClientAllowBinary, kSantaUserClientDenyBinary, kSantaUserClientClearCache, diff --git a/Source/santa-driver/SantaDecisionManager.cc b/Source/santa-driver/SantaDecisionManager.cc index 055e96d35..ce3d02f34 100644 --- a/Source/santa-driver/SantaDecisionManager.cc +++ b/Source/santa-driver/SantaDecisionManager.cc @@ -19,40 +19,15 @@ OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject); #pragma mark Object Lifecycle -SantaDecisionManager *SantaDecisionManager::WithQueueAndPID( - IOSharedDataQueue *queue, pid_t pid) { - SantaDecisionManager *me = new SantaDecisionManager; +bool SantaDecisionManager::init() { + dataqueue_lock_ = IORWLockAlloc(); + cached_decisions_lock_ = IORWLockAlloc(); + cached_decisions_ = OSDictionary::withCapacity(1000); - if (me && !me->InitWithQueueAndPID(queue, pid)) { - me->free(); - return NULL; - } - - return me; -} - -bool SantaDecisionManager::InitWithQueueAndPID( - IOSharedDataQueue *queue, pid_t pid) { - if (!super::init()) return false; - - if (!pid) return false; - if (!queue) return false; - - listener_invocations_ = 0; - dataqueue_ = queue; - owning_pid_ = pid; - owning_proc_ = proc_find(pid); - - if (!(dataqueue_lock_ = IORWLockAlloc())) return FALSE; - if (!(cached_decisions_lock_ = IORWLockAlloc())) return FALSE; - if (!(cached_decisions_ = OSDictionary::withCapacity(1000))) return FALSE; - - return TRUE; + return kIOReturnSuccess; } void SantaDecisionManager::free() { - proc_rele(owning_proc_); - if (cached_decisions_) { cached_decisions_->release(); cached_decisions_ = NULL; @@ -63,7 +38,7 @@ void SantaDecisionManager::free() { cached_decisions_lock_ = NULL; } - if (dataqueue_lock_) { + if (dataqueue_lock_ ) { IORWLockFree(dataqueue_lock_); dataqueue_lock_ = NULL; } @@ -71,7 +46,87 @@ void SantaDecisionManager::free() { super::free(); } -# pragma mark Cache Management +#pragma mark Client Management + +void SantaDecisionManager::ConnectClient(IOSharedDataQueue *queue, pid_t pid) { + if (!pid) return; + if (!queue) return; + + // Any decisions made while the daemon wasn't + // connected should be cleared + cached_decisions_->flushCollection(); + + dataqueue_ = queue; + dataqueue_->retain(); + + owning_pid_ = pid; + owning_proc_ = proc_find(pid); +} + +void SantaDecisionManager::DisconnectClient() { + owning_pid_ = -1; + + // Ask santad to shutdown, in case it's running. + santa_message_t message; + message.action = ACTION_REQUEST_SHUTDOWN; + message.userId = 0; + message.pid = 0; + message.ppid = 0; + message.vnode_id = 0; + PostToQueue(message); + + dataqueue_->release(); + dataqueue_ = NULL; + + proc_rele(owning_proc_); + owning_proc_ = NULL; +} + +bool SantaDecisionManager::ClientConnected() { + return owning_pid_ > 0; +} + +# pragma mark Listener Control + +kern_return_t SantaDecisionManager::StartListener() { + process_listener_ = kauth_listen_scope(KAUTH_SCOPE_PROCESS, + process_scope_callback, + reinterpret_cast(this)); + if (!process_listener_) return kIOReturnInternalError; + LOGD("Process listener started."); + + vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE, + vnode_scope_callback, + reinterpret_cast(this)); + if (!vnode_listener_) return kIOReturnInternalError; + + LOGD("Vnode listener started."); + + return kIOReturnSuccess; +} + +kern_return_t SantaDecisionManager::StopListener() { + kauth_unlisten_scope(vnode_listener_); + vnode_listener_ = NULL; + + kauth_unlisten_scope(process_listener_); + process_listener_ = NULL; + + // Wait for any active invocations to finish before returning + do { + IOSleep(5); + } while (listener_invocations_); + + // Delete any cached decisions + ClearCache(); + + LOGD("Vnode listener stopped."); + LOGD("Process listener stopped."); + + return kIOReturnSuccess; +} + +#pragma mark Cache Management void SantaDecisionManager::AddToCache( const char *identifier, santa_action_t decision, uint64_t microsecs) { @@ -169,7 +224,10 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) { bool SantaDecisionManager::PostToQueue(santa_message_t message) { IORWLockWrite(dataqueue_lock_); - bool kr = dataqueue_->enqueue(&message, sizeof(message)); + bool kr = false; + if (dataqueue_) { + kr = dataqueue_->enqueue(&message, sizeof(message)); + } IORWLockUnlock(dataqueue_lock_); return kr; } @@ -200,6 +258,15 @@ santa_action_t SantaDecisionManager::FetchDecision( path[0] = '\0'; } + // If daemon isn't connected, allow and cache + if (owning_pid_ < 1) { + LOGI("Exeuction request without daemon running: %s", path); + AddToCache(vnode_id_str, + ACTION_RESPOND_CHECKBW_ALLOW, + GetCurrentUptime()); + return ACTION_RESPOND_CHECKBW_ALLOW; + } + // Prepare to send message to daemon santa_message_t message; strncpy(message.path, path, MAX_PATH_LEN); @@ -260,10 +327,6 @@ uint64_t SantaDecisionManager::GetCurrentUptime() { # pragma mark Invocation Tracking & PID comparison -SInt32 SantaDecisionManager::GetListenerInvocations() { - return listener_invocations_; -} - void SantaDecisionManager::IncrementListenerInvocations() { OSIncrementAtomic(&listener_invocations_); } @@ -276,46 +339,6 @@ bool SantaDecisionManager::MatchesOwningPID(const pid_t other_pid) { return (owning_pid_ == other_pid); } -# pragma mark Listener Control - -kern_return_t SantaDecisionManager::StartListener() { - process_listener_ = kauth_listen_scope(KAUTH_SCOPE_PROCESS, - process_scope_callback, - reinterpret_cast(this)); - if (!process_listener_) return kIOReturnInternalError; - LOGD("Process listener started."); - - vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE, - vnode_scope_callback, - reinterpret_cast(this)); - if (!vnode_listener_) return kIOReturnInternalError; - - LOGD("Vnode listener started."); - - return kIOReturnSuccess; -} - -kern_return_t SantaDecisionManager::StopListener() { - kauth_unlisten_scope(vnode_listener_); - vnode_listener_ = NULL; - - kauth_unlisten_scope(process_listener_); - process_listener_ = NULL; - - // Wait for any active invocations to finish before returning - do { - IOSleep(5); - } while (GetListenerInvocations()); - - // Delete any cached decisions - ClearCache(); - - LOGD("Vnode listener stopped."); - LOGD("Process listener stopped."); - - return kIOReturnSuccess; -} - #undef super #pragma mark Kauth Callbacks diff --git a/Source/santa-driver/SantaDecisionManager.h b/Source/santa-driver/SantaDecisionManager.h index 5519586ea..48d7dd687 100644 --- a/Source/santa-driver/SantaDecisionManager.h +++ b/Source/santa-driver/SantaDecisionManager.h @@ -66,47 +66,77 @@ class SantaDecisionManager : public OSObject { OSDeclareDefaultStructors(SantaDecisionManager); public: - // Convenience constructor - // Queue remains owned by caller but must exist for lifetime of - // SantaDecisionManager instance. - static SantaDecisionManager *WithQueueAndPID( - IOSharedDataQueue *queue, pid_t pid); + /// Used for initialization after instantiation. Required because + /// constructors cannot throw inside kernel-space. + bool init(); - bool InitWithQueueAndPID(IOSharedDataQueue *queue, pid_t pid); + /// Called automatically when retain count drops to 0. void free(); - // Decision Fetching / Daemon Communication - bool PostToQueue(santa_message_t); - santa_action_t FetchDecision(const kauth_cred_t credential, - const vfs_context_t vfs_context, - const vnode_t vnode); + /// Called by SantaDriverClient when a client connects, providing the data + /// queue used to pass messages and the pid of the client process. + void ConnectClient(IOSharedDataQueue *queue, pid_t pid); - // Vnode ID string - uint64_t GetVnodeIDForVnode(const vfs_context_t context, const vnode_t vp); + /// Called by SantaDriverClient when a client disconnects + void DisconnectClient(); + + /// Returns whether a client is currently connected or not. + bool ClientConnected(); + + /// Starts both kauth listeners. + kern_return_t StartListener(); - // Cache management + /// Stops both kauth listeners. After stopping new callback requests, + /// waits until all current invocations have finished before clearing the + /// cache and returning. + kern_return_t StopListener(); + + /// Adds a decision to the cache, with a timestamp. void AddToCache(const char *identifier, const santa_action_t decision, const uint64_t microsecs); + + /// Checks to see if a given identifier is in the cache and removes it. void CacheCheck(const char *identifier); + + /// Returns the number of entries in the cache. uint64_t CacheCount(); + + /// Clears the cache. void ClearCache(); + + /// Fetches a response from the cache, first checking to see if the + /// entry has expired. santa_action_t GetFromCache(const char *identifier); - // Listener invocation management - SInt32 GetListenerInvocations(); - void IncrementListenerInvocations(); - void DecrementListenerInvocations(); + /// Posts the requested message to the client data queue, if there is one. + /// Uses dataqueue_lock_ to ensure two threads don't try to write to the + /// queue at the same time. + bool PostToQueue(santa_message_t); - // Owning PID comparison - bool MatchesOwningPID(const pid_t other_pid); + /// Fetches an execution decision for a file, first using the cache and then + /// by sending a message to the daemon and waiting until a response arrives. + /// If a daemon isn't connected, will allow execution and cache, logging + /// the path to the executed file. + santa_action_t FetchDecision(const kauth_cred_t credential, + const vfs_context_t vfs_context, + const vnode_t vnode); + + /// Fetches the vnode_id for a given vnode. + uint64_t GetVnodeIDForVnode(const vfs_context_t context, const vnode_t vp); - // Returns the current system uptime in microseconds + /// Returns the current system uptime in microseconds uint64_t GetCurrentUptime(); - // Starting and stopping the listener - kern_return_t StartListener(); - kern_return_t StopListener(); + + /// Increments the count of active vnode callback's pending. + void IncrementListenerInvocations(); + + /// Decrements the count of active vnode callback's pending. + void DecrementListenerInvocations(); + + /// Returns true if other_pid is the same as the current client pid. + bool MatchesOwningPID(const pid_t other_pid); private: OSDictionary *cached_decisions_; diff --git a/Source/santa-driver/SantaDriver.cc b/Source/santa-driver/SantaDriver.cc index febf67f26..7d75502f7 100644 --- a/Source/santa-driver/SantaDriver.cc +++ b/Source/santa-driver/SantaDriver.cc @@ -23,6 +23,10 @@ OSDefineMetaClassAndStructors(com_google_SantaDriver, IOService); bool SantaDriver::start(IOService *provider) { if (!super::start(provider)) return false; + santaDecisionManager = new SantaDecisionManager; + santaDecisionManager->init(); + santaDecisionManager->StartListener(); + registerService(); LOGI("Loaded, version %s.", OSKextGetCurrentVersionString()); @@ -31,7 +35,17 @@ bool SantaDriver::start(IOService *provider) { } void SantaDriver::stop(IOService *provider) { + santaDecisionManager->StopListener(); + santaDecisionManager->release(); + santaDecisionManager = NULL; + LOGI("Unloaded."); + + super::stop(provider); +} + +SantaDecisionManager* SantaDriver::GetDecisionManager() { + return santaDecisionManager; } #undef super diff --git a/Source/santa-driver/SantaDriver.h b/Source/santa-driver/SantaDriver.h index 072413045..785f62816 100644 --- a/Source/santa-driver/SantaDriver.h +++ b/Source/santa-driver/SantaDriver.h @@ -22,14 +22,26 @@ #include "SNTLogging.h" /// -/// The driver class, which provides just the start/stop functions. +/// The driver class, which provides the start/stop functions and holds +/// the SantaDecisionManager instance which the connected client +/// communicates with. /// class com_google_SantaDriver : public IOService { OSDeclareDefaultStructors(com_google_SantaDriver); public: + /// Called by the kernel when the kext is loaded bool start(IOService *provider); + + /// Called by the kernel when the kext is unloaded void stop(IOService *provider); + + /// Returns a pointer to the SantaDecisionManager created in start(). + SantaDecisionManager* GetDecisionManager(); + + private: + SantaDecisionManager *santaDecisionManager; + }; #endif // SANTA__SANTA_DRIVER__SANTADRIVER_H diff --git a/Source/santa-driver/SantaDriverClient.cc b/Source/santa-driver/SantaDriverClient.cc index b13650882..335ca4a4f 100644 --- a/Source/santa-driver/SantaDriverClient.cc +++ b/Source/santa-driver/SantaDriverClient.cc @@ -20,7 +20,7 @@ // The defines above can'be used in this function, must use the full names. OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient); -# pragma mark Driver Management +#pragma mark Driver Management bool SantaDriverClient::initWithTask( task_t owningTask, void *securityID, UInt32 type) { @@ -41,52 +41,38 @@ bool SantaDriverClient::start(IOService *provider) { if (!fProvider) return false; if (!super::start(provider)) return false; - fSDMLock = IOLockAlloc(); + fSDM = fProvider->GetDecisionManager(); return true; } void SantaDriverClient::stop(IOService *provider) { super::stop(provider); + fProvider = NULL; } IOReturn SantaDriverClient::clientClose() { - close(); terminate(kIOServiceSynchronous); - - fProvider = NULL; - return kIOReturnSuccess; } bool SantaDriverClient::terminate(IOOptionBits options) { - // We have to lock before this check in case the client exits and the kext - // is unloaded very shortly afterwards. - IOLockLock(fSDMLock); - if (fSDM) { - fSDM->StopListener(); - - // Ask santad to shutdown - santa_message_t message; - message.action = ACTION_REQUEST_SHUTDOWN; - message.userId = 0; - message.pid = 0; - message.ppid = 0; - message.vnode_id = 0; - fSDM->PostToQueue(message); - - LOGI("Client disconnected."); - - fSDM->release(); - fSDM = NULL; - } - IOLockUnlock(fSDMLock); + fSDM->DisconnectClient(); + LOGI("Client disconnected."); + + fSharedMemory->release(); + fDataQueue->release(); + + fSharedMemory = NULL; + fDataQueue = NULL; if (fProvider && fProvider->isOpen(this)) fProvider->close(this); return super::terminate(options); } +#pragma mark Fetching memory and data queue notifications + IOReturn SantaDriverClient::registerNotificationPort(mach_port_t port, UInt32 type, UInt32 ref) { @@ -108,7 +94,7 @@ IOReturn SantaDriverClient::clientMemoryForType(UInt32 type, fSharedMemory->retain(); // client will decrement this ref *memory = fSharedMemory; - return fSDM->StartListener(); + return kIOReturnSuccess; } return kIOReturnNoMemory; @@ -136,9 +122,7 @@ IOReturn SantaDriverClient::open() { return kIOReturnVMError; } - IOLockLock(fSDMLock); - fSDM = SantaDecisionManager::WithQueueAndPID(fDataQueue, proc_selfpid()); - IOLockUnlock(fSDMLock); + fSDM->ConnectClient(fDataQueue, proc_selfpid()); LOGI("Client connected, PID: %d.", proc_selfpid()); @@ -153,21 +137,6 @@ IOReturn SantaDriverClient::static_open( return target->open(); } -IOReturn SantaDriverClient::close() { - if (!fProvider) return kIOReturnNotAttached; - if (fProvider->isOpen(this)) fProvider->close(this); - - return kIOReturnSuccess; -} - -IOReturn SantaDriverClient::static_close( - SantaDriverClient *target, - void *reference, - IOExternalMethodArguments *arguments) { - if (!target) return kIOReturnBadArgument; - return target->close(); -} - IOReturn SantaDriverClient::allow_binary(const uint64_t vnode_id) { char vnode_id_str[21]; snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id); @@ -240,8 +209,8 @@ IOReturn SantaDriverClient::externalMethod( IOExternalMethodDispatch *dispatch, OSObject *target, void *reference) { - // Array of methods callable by clients. The order of these must match the - // order of the items in |SantaDriverMethods| in SNTKernelCommon.h + /// Array of methods callable by clients. The order of these must match the + /// order of the items in SantaDriverMethods in SNTKernelCommon.h IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = { { reinterpret_cast(&SantaDriverClient::static_open), @@ -250,14 +219,6 @@ IOReturn SantaDriverClient::externalMethod( 0, // output scalar 0 // output struct }, - { - reinterpret_cast( - &SantaDriverClient::static_close), - 0, - 0, - 0, - 0 - }, { reinterpret_cast( &SantaDriverClient::static_allow_binary), diff --git a/Source/santa-driver/SantaDriverClient.h b/Source/santa-driver/SantaDriverClient.h index 200034b26..206e45656 100644 --- a/Source/santa-driver/SantaDriverClient.h +++ b/Source/santa-driver/SantaDriverClient.h @@ -38,7 +38,7 @@ const int kMaxQueueEvents = 64; /// /// Documentation on how the IOUserClient parts of this code work can be found /// here: -/// @link https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html +/// https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html /// class com_google_SantaDriverClient : public IOUserClient { OSDeclareDefaultStructors(com_google_SantaDriverClient); @@ -48,61 +48,75 @@ class com_google_SantaDriverClient : public IOUserClient { IOMemoryDescriptor *fSharedMemory; com_google_SantaDriver *fProvider; SantaDecisionManager *fSDM; - IOLock *fSDMLock; public: + /// Called as part of IOServiceOpen in clients + bool initWithTask(task_t owningTask, void *securityID, UInt32 type); + + /// Called after initWithTask as part of IOServiceOpen bool start(IOService *provider); + + /// Called when this class is stopping void stop(IOService *provider); + + /// Called when a client disconnects IOReturn clientClose(); + + /// Called when the driver is shutting down bool terminate(IOOptionBits options); - bool initWithTask(task_t owningTask, void *securityID, UInt32 type); + /// Called in clients with IOConnectSetNotificationPort IOReturn registerNotificationPort( mach_port_t port, UInt32 type, UInt32 refCon); + /// Called in clients with IOConnectMapMemory IOReturn clientMemoryForType( UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory); + /// Called in clients with IOConnectCallScalarMethod etc. Dispatches + /// to the requested selector using the SantaDriverMethods enum in + /// SNTKernelCommon. IOReturn externalMethod( UInt32 selector, IOExternalMethodArguments *arguments, IOExternalMethodDispatch *dispatch, OSObject *target, void *reference); + /// + /// The userpsace callable methods are below. Each method corresponds + /// to an entry in SantaDriverMethods. Each method has a static version + /// which just calls the method on the provided target. + /// + + /// Called during client connection IOReturn open(); static IOReturn static_open( com_google_SantaDriverClient *target, void *reference, IOExternalMethodArguments *arguments); - IOReturn close(); - static IOReturn static_close( - com_google_SantaDriverClient *target, - void *reference, - IOExternalMethodArguments *arguments); - - /// The daemon calls this to allow a binary. + /// The daemon calls this to allow a binary. IOReturn allow_binary(uint64_t vnode_id); static IOReturn static_allow_binary( com_google_SantaDriverClient *target, void *reference, IOExternalMethodArguments *arguments); - /// The daemon calls this to deny a binary. + /// The daemon calls this to deny a binary. IOReturn deny_binary(uint64_t vnode_id); static IOReturn static_deny_binary( com_google_SantaDriverClient *target, void *reference, IOExternalMethodArguments *arguments); - /// The daemon calls this to empty the cache. + /// The daemon calls this to empty the cache. IOReturn clear_cache(); static IOReturn static_clear_cache( com_google_SantaDriverClient *target, void *reference, IOExternalMethodArguments *arguments); - /// The daemon calls this to find out how many items are in the cache + /// The daemon calls this to find out how many items are in the cache IOReturn cache_count(uint64_t *output); static IOReturn static_cache_count( com_google_SantaDriverClient *target,