santa-driver is a macOS kernel extension (KEXT) that makes use of the Kernel Authorization (Kauth) KPI. This allows santa-driver to listen for events and either deny or defer the decision of those events. The santa-driver acts as an intermediary layer between Kauth and santad, with some caching to lower the overhead of decision making.
santa-driver utilizes two Kauth scopes
KAUTH_SCOPE_FILEOP. It registers itself with the Kauth API by calling
kauth_listen_scope() for each scope. This function takes three arguments:
const char *scope
It returns a
kauth_listener_t that is stored for later use, in Santa’s case to stop listening.
Here is how santa-driver starts listening for
vnode_listener_ = kauth_listen_scope( KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
vnode_scope_callback is called for every vnode event. There are many types of vnode events, they complete list can be viewed in the kauth.h. There are many types of vnode events, the complete list can be viewed in kauth.h. Santa is only concerned with regular files generating
KAUTH_VNODE_EXECUTE  and
KAUTH_VNODE_WRITE_DATA events. All non-regular files and unnecessary vnode events are filtered out.
Here is how santa-driver stops listening for
KAUTH_VNODE_EXECUTE events that do not have the
KAUTH_VNODE_ACCESS advisory bit set.
Santa also listens for file operations, this is mainly used for logging  and cache invalidation.
KAUTH_FILEOP_EXECis used to log
execve()s. Since the
KAUTH_VNODE_EXECUTEis used to allow or deny an
execve()the process arguments have not been setup yet. Since
KAUTH_FILEOP_EXECis triggered after an
execve()it is used to log the
KAUTH_FILEOP_CLOSE is used to invalidate that file’s representation in the cache. If a file has changed it needs to be re-evalauted. This is particularly necessary for files that were added to the cache with an action of allow.
santa-driver implements an IOUserClient subclass and santad interacts with it through IOKit/IOKitLib.h functions.
To aid in performance, santa-driver utilizes a caching system to hold the state of all observed
The key is a
uint64_t. The top 32 bits hold the filesystem ID, while the bottom 32 bits hold the file unique ID. Together we call this the vnode_id.
uint64_t vnode_id = (((uint64_t)fsid << 32) | fileid);
The value is a
uint64_t containing the action necessary, along with the decision timestamp. The action is stored in the top 8 bits. The decision timestamp is stored in the remaining 56 bits.
santa_action_t action = (santa_action_t)(cache_val >> 56); uint64_t decision_time = (cache_val & ~(0xFF00000000000000));
The possible actions are:
| ||None||Awaiting an allow or deny|
|decision from santad.|
| ||None||Allow the |
| ||500 milliseconds||Deny the |
|re-evalaute after 500|
|milliseconds. If someone is|
|trying to run a banned binary|
|continually every millisecond|
|for example, only 2 evaluation|
|requests to santad for would|
|occur per second. This|
|mitigates a denial of service|
|type attack on santad.|
Besides the expiry time for individual entries, the entire cache will be cleared if any of the following events takes place:
- Addition of a block rule
- Change to the blocked path regex
- Cache fills up. This defaults to
5000entries for the root volume and
500for all other mounted volumes.
To view the current kernel cache count see the “Kernel info” section of
⇒ santactl status >>> Kernel Info Root cache count | 107 Non-root cache count | 0