ClamAV is an open source (GPLv2) anti-virus toolkit.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
clamav/contrib/ClamAuth/ClamAuth.c

884 lines
30 KiB

/*
* Copyright (c) 2007 by Apple Computer, Inc., All Rights Reserved.
* Copyright (c) 2011 Sourcefire, Inc.
*/
#include <kern/assert.h>
#include <mach/mach_types.h>
#include <libkern/libkern.h>
#include <libkern/OSAtomic.h>
#include <libkern/OSMalloc.h>
#include <sys/sysctl.h>
#include <sys/kauth.h>
#include <sys/vnode.h>
#pragma mark ***** Global Resources
/* These declarations are required to allocate memory and create locks.
* They're created when we start and destroyed when we stop.
*/
static OSMallocTag gMallocTag = NULL;
static lck_grp_t * gLockGroup = NULL;
#pragma mark ***** Vnode Utilities
/* VnodeActionInfo describes one of the action bits in the vnode scope's action
* field.
*/
struct VnodeActionInfo {
kauth_action_t fMask; /* only one bit should be set */
const char * fOpNameFile; /* descriptive name of the bit for files */
const char * fOpNameDir; /* descriptive name of the bit for directories
* NULL implies equivalent to fOpNameFile
*/
};
typedef struct VnodeActionInfo VnodeActionInfo;
/* Some evil macros (aren't they all) to make it easier to initialise kVnodeActionInfo. */
#define VNODE_ACTION(action) { KAUTH_VNODE_ ## action, #action, NULL }
#define VNODE_ACTION_FILEDIR(actionFile, actionDir) { KAUTH_VNODE_ ## actionFile, #actionFile, #actionDir }
/* kVnodeActionInfo is a table of all the known action bits and their human readable names. */
static const VnodeActionInfo kVnodeActionInfo[] = {
VNODE_ACTION_FILEDIR(READ_DATA, LIST_DIRECTORY),
VNODE_ACTION_FILEDIR(WRITE_DATA, ADD_FILE),
VNODE_ACTION_FILEDIR(EXECUTE, SEARCH),
VNODE_ACTION(DELETE),
VNODE_ACTION_FILEDIR(APPEND_DATA, ADD_SUBDIRECTORY),
VNODE_ACTION(DELETE_CHILD),
VNODE_ACTION(READ_ATTRIBUTES),
VNODE_ACTION(WRITE_ATTRIBUTES),
VNODE_ACTION(READ_EXTATTRIBUTES),
VNODE_ACTION(WRITE_EXTATTRIBUTES),
VNODE_ACTION(READ_SECURITY),
VNODE_ACTION(WRITE_SECURITY),
VNODE_ACTION(TAKE_OWNERSHIP),
VNODE_ACTION(SYNCHRONIZE),
VNODE_ACTION(LINKTARGET),
VNODE_ACTION(CHECKIMMUTABLE),
VNODE_ACTION(ACCESS),
VNODE_ACTION(NOIMMUTABLE)
};
#define kVnodeActionInfoCount (sizeof(kVnodeActionInfo) / sizeof(*kVnodeActionInfo))
static int CreateVnodeActionString(
kauth_action_t action,
boolean_t isDir,
char ** actionStrPtr,
size_t * actionStrBufSizePtr
)
/* Creates a human readable description of a vnode action bitmap.
* action is the bitmap. isDir is true if the action relates to a
* directory, and false otherwise. This allows the action name to
* be context sensitive (KAUTH_VNODE_EXECUTE vs KAUTH_VNODE_SEARCH).
* actionStrPtr is a place to store the allocated string pointer.
* The caller is responsible for freeing this memory using OSFree.
* actionStrBufSizePtr is a place to store the size of the resulting
* allocation (because the annoying kernel memory allocator requires
* you to provide the size when you free).
*/
{
int err;
enum { kCalcLen, kCreateString } pass;
kauth_action_t actionsLeft;
unsigned int infoIndex;
size_t actionStrLen;
char * actionStr;
assert( actionStrPtr != NULL);
assert(*actionStrPtr != NULL);
assert( actionStrBufSizePtr != NULL);
err = 0;
actionStr = NULL;
/* A two pass algorithm. In the first pass, actionStr is NULL and we just
* calculate actionStrLen; at the end of the first pass we actually allocate
* actionStr. In the second pass, actionStr is not NULL and we actually
* initialise the string in that buffer.
*/
for (pass = kCalcLen; pass <= kCreateString; pass++) {
actionsLeft = action;
/* Process action bits that are described in kVnodeActionInfo. */
infoIndex = 0;
actionStrLen = 0;
while ( (actionsLeft != 0) && (infoIndex < kVnodeActionInfoCount) ) {
if ( actionsLeft & kVnodeActionInfo[infoIndex].fMask ) {
const char * thisStr;
size_t thisStrLen;
/* Increment the length of the action string by the action name. */
if ( isDir && (kVnodeActionInfo[infoIndex].fOpNameDir != NULL) ) {
thisStr = kVnodeActionInfo[infoIndex].fOpNameDir;
} else {
thisStr = kVnodeActionInfo[infoIndex].fOpNameFile;
}
thisStrLen = strlen(thisStr);
if (actionStr != NULL) {
memcpy(&actionStr[actionStrLen], thisStr, thisStrLen);
}
actionStrLen += thisStrLen;
/* Now clear the bit in actionsLeft, indicating that we've
* processed this one.
*/
actionsLeft &= ~kVnodeActionInfo[infoIndex].fMask;
/* If there's any actions left, account for the intervening "|". */
if (actionsLeft != 0) {
if (actionStr != NULL) {
actionStr[actionStrLen] = '|';
}
actionStrLen += 1;
}
}
infoIndex += 1;
}
/* Now include any remaining actions as a hex number. */
if (actionsLeft != 0) {
if (actionStr != NULL)
snprintf(&actionStr[actionStrLen], 10, "0x%08x", actionsLeft);
actionStrLen += 10; /* strlen("0x") + 8 chars of hex */
}
/* If we're at the end of the first pass, allocate actionStr
* based on the size we just calculated. Remember that actionStrLen
* is a string length, so we have to allocate an extra character to
* account for the null terminator. If we're at the end of the
* second pass, just place the null terminator.
*/
if (pass == kCalcLen) {
actionStr = OSMalloc(actionStrLen + 1, gMallocTag);
if (actionStr == NULL) {
err = ENOMEM;
}
} else {
actionStr[actionStrLen] = 0;
}
if (err != 0) {
break;
}
}
*actionStrPtr = actionStr;
*actionStrBufSizePtr = actionStrLen + 1;
assert( (err == 0) == (*actionStrPtr != NULL) );
return err;
}
static int CreateVnodePath(vnode_t vp, char **vpPathPtr)
/* Creates a full path for a vnode. vp may be NULL, in which
* case the returned path is NULL (that is, no memory is allocated).
* vpPathPtr is a place to store the allocated path buffer.
* The caller is responsible for freeing this memory using OSFree
* (the size is always MAXPATHLEN).
*/
{
int err;
int pathLen;
assert( vpPathPtr != NULL);
assert(*vpPathPtr == NULL);
err = 0;
if (vp != NULL) {
*vpPathPtr = OSMalloc(MAXPATHLEN, gMallocTag);
if (*vpPathPtr == NULL) {
err = ENOMEM;
}
if (err == 0) {
pathLen = MAXPATHLEN;
err = vn_getpath(vp, *vpPathPtr, &pathLen);
}
}
return err;
}
#pragma mark ***** Listener Resources
/* Some scopes (for example KAUTH_SCOPE_VNODE) are called a /lot/. Thus,
* it's a good idea to avoid taking mutexes in your listener if at all
* possible. Thus, we use non-blocking synchronisation to protect the
* global data that's accessed by our listener (gPrefix and gListenerScope).
* Every time we enter a listener, we increment gActivationCount, and ever
* time we leave we decrement it. When we want to change the listener, we
* first remove the listener, then we wait for the activation count to hit,
* then we can modify the globals protected by that activation count.
*
* IMPORTANT:
* There is still a race condition here. See RemoveListener for a description
* of the race and why we can't fix it.
*/
static SInt32 gActivationCount = 0;
static const char * gPrefix = NULL; /* points into gConfiguration, so doesn't need to be freed */
static char * gListenerScope = NULL; /* must be freed using OSFree */
static int GenericScopeListener(
kauth_cred_t credential,
void * idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3
)
/* A Kauth listener that's called to authorize an action in the generic
* scope (KAUTH_SCOPE_GENERIC). See the Kauth documentation for a description
* of the parameters. In this case, we just dump out the parameters to the
* operation and return KAUTH_RESULT_DEFER, allowing the other listeners
* to decide whether the operation is allowed or not.
*/
{
#pragma unused(idata)
#pragma unused(arg0)
#pragma unused(arg1)
#pragma unused(arg2)
#pragma unused(arg3)
(void) OSIncrementAtomic(&gActivationCount);
/* Tell the user about this request. */
switch (action) {
case KAUTH_GENERIC_ISSUSER:
printf(
"scope=" KAUTH_SCOPE_GENERIC ", action=KAUTH_GENERIC_ISSUSER, actor=%ld\n",
(long) kauth_cred_getuid(credential)
);
break;
default:
printf("ClamAuth.GenericScopeListener: Unknown action (%d).\n", action);
break;
}
(void) OSDecrementAtomic(&gActivationCount);
return KAUTH_RESULT_DEFER;
}
static int ProcessScopeListener(
kauth_cred_t credential,
void * idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3
)
/* A Kauth listener that's called to authorize an action in the process
* scope (KAUTH_SCOPE_PROCESS). See the Kauth documentation for a description
* of the parameters. In this case, we just dump out the parameters to the
* operation and return KAUTH_RESULT_DEFER, allowing the other listeners
* to decide whether the operation is allowed or not.
*/
{
#pragma unused(idata)
#pragma unused(arg2)
#pragma unused(arg3)
(void) OSIncrementAtomic(&gActivationCount);
/* Tell the user about this request. */
switch (action) {
case KAUTH_PROCESS_CANSIGNAL:
printf(
"scope=" KAUTH_SCOPE_PROCESS ", action=KAUTH_PROCESS_CANSIGNAL, uid=%ld, pid=%ld, target=%ld, signal=%ld\n",
(long) kauth_cred_getuid(credential),
(long) proc_selfpid(),
(long) proc_pid((proc_t) arg0),
(long) arg1
);
break;
case KAUTH_PROCESS_CANTRACE:
printf(
"scope=" KAUTH_SCOPE_PROCESS ", action=KAUTH_PROCESS_CANTRACE, uid=%ld, pid=%ld, target=%ld\n",
(long) kauth_cred_getuid(credential),
(long) proc_selfpid(),
(long) proc_pid((proc_t) arg0)
);
break;
default:
printf("ClamAuth.ProcessScopeListener: Unknown action (%d).\n", action);
break;
}
(void) OSDecrementAtomic(&gActivationCount);
return KAUTH_RESULT_DEFER;
}
static int VnodeScopeListener(
kauth_cred_t credential,
void * idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3
)
/* A Kauth listener that's called to authorize an action in the vnode scope */
{
#pragma unused(credential)
#pragma unused(idata)
#pragma unused(arg3)
int err;
vfs_context_t context;
vnode_t vp;
vnode_t dvp;
char * vpPath;
char * dvpPath;
boolean_t isDir;
char * actionStr;
size_t actionStrBufSize;
actionStrBufSize = 0;
(void) OSIncrementAtomic(&gActivationCount);
context = (vfs_context_t) arg0;
vp = (vnode_t) arg1;
dvp = (vnode_t) arg2;
vpPath = NULL;
dvpPath = NULL;
actionStr = NULL;
/* Convert the vnode, if any, to a path. */
err = CreateVnodePath(vp, &vpPath);
/* Convert the parent directory vnode, if any, to a path. */
if (err == 0)
err = CreateVnodePath(dvp, &dvpPath);
/* Create actionStr as a human readable description of action. */
if (err == 0) {
if (vp != NULL) {
isDir = ( vnode_vtype(vp) == VDIR );
} else {
isDir = FALSE;
}
err = CreateVnodeActionString(action, isDir, &actionStr, &actionStrBufSize);
}
/* Tell the user about this request. Note that we filter requests
* based on gPrefix. If gPrefix is set, only requests where one
* of the paths is prefixed by gPrefix will be printed.
*/
if (err == 0) {
if ( (gPrefix == NULL)
|| ( ( (vpPath != NULL) && strprefix(vpPath, gPrefix) )
|| ( (dvpPath != NULL) && strprefix(dvpPath, gPrefix) )
)
) {
printf(
"scope=" KAUTH_SCOPE_VNODE ", action=%s, uid=%ld, vp=%s, dvp=%s\n",
actionStr,
(long) kauth_cred_getuid(vfs_context_ucred(context)),
(vpPath != NULL) ? vpPath : "<null>",
(dvpPath != NULL) ? dvpPath : "<null>"
);
}
} else {
printf("ClamAuth.VnodeScopeListener: Error %d.\n", err);
}
/* Clean up. */
if (actionStr != NULL) {
OSFree(actionStr, actionStrBufSize, gMallocTag);
}
if (vpPath != NULL) {
OSFree(vpPath, MAXPATHLEN, gMallocTag);
}
if (dvpPath != NULL) {
OSFree(dvpPath, MAXPATHLEN, gMallocTag);
}
(void) OSDecrementAtomic(&gActivationCount);
return KAUTH_RESULT_DEFER;
}
static int FileOpScopeListener(
kauth_cred_t credential,
void * idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3
)
/* A Kauth listener that's called to authorize an action in the file operation */
{
#pragma unused(idata)
#pragma unused(arg2)
#pragma unused(arg3)
(void) OSIncrementAtomic(&gActivationCount);
/* Tell the user about this request. Note that we filter requests
* based on gPrefix. If gPrefix is set, only requests there is a
* path that's prefixed by gPrefix will be printed.
*/
switch (action) {
case KAUTH_FILEOP_OPEN:
if ( (gPrefix == NULL) || strprefix( (const char *) arg1, gPrefix) ) {
printf(
"scope=" KAUTH_SCOPE_FILEOP ", action=KAUTH_FILEOP_OPEN, uid=%ld, vnode=0x%lx, path=%s\n",
(long) kauth_cred_getuid(credential),
(long) arg0,
(const char *) arg1
);
}
break;
case KAUTH_FILEOP_EXEC:
if ( (gPrefix == NULL) || strprefix( (const char *) arg1, gPrefix) ) {
printf(
"scope=" KAUTH_SCOPE_FILEOP ", action=KAUTH_FILEOP_EXEC, uid=%ld, vnode=0x%lx, path=%s\n",
(long) kauth_cred_getuid(credential),
(long) arg0,
(const char *) arg1
);
}
break;
default:
printf("ClamAuth.FileOpScopeListener: Unknown action (%d).\n", action);
break;
}
(void) OSDecrementAtomic(&gActivationCount);
return KAUTH_RESULT_DEFER;
}
static int UnknownScopeListener(
kauth_cred_t credential,
void * idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3
)
/* A Kauth listener that's called to authorize an action in any scope
* that we don't recognise).
*/
{
#pragma unused(idata)
(void) OSIncrementAtomic(&gActivationCount);
/* Tell the user about this request. */
printf(
"scope=%s, action=%d, uid=%ld, arg0=0x%lx, arg1=0x%lx, arg2=0x%lx, arg3=0x%lx\n",
gListenerScope,
action,
(long) kauth_cred_getuid(credential),
(long) arg0,
(long) arg1,
(long) arg2,
(long) arg3
);
(void) OSDecrementAtomic(&gActivationCount);
return KAUTH_RESULT_DEFER;
}
#pragma mark ***** Listener Install/Remove
/* gConfigurationLock is a mutex that protects us from two threads trying to
* simultaneously modify the configuration. The configuration is protect in
* N ways:
*
* o During startup, we register our sysctl OID last, so no one can start
* modifying the configuration until everything is set up nicely.
*
* o During normal operations, the sysctl handler (SysctlHandler) takes
* the lock to prevent two threads from reconfiguring the system at the
* same time.
*
* o During termination, the stop routine first removes the sysctl OID
* and then takes the lock before it removes the listener. The first
* act prevents any new sysctl requests coming it, the second blocks
* until current sysctl requests are done.
*
* IMPORTANT:
* There is still a race condition here. See the stop routine for a description
* of the race and why we can't fix it.
*/
static lck_mtx_t * gConfigurationLock = NULL;
/* gListener is our handle to the installed scope listener. We need to
* keep it around so that we can remove the listener when we're done.
*/
static kauth_listener_t gListener = NULL;
static void RemoveListener(void)
/* Removes the installed scope listener, if any.
*
* Under almost all circumstances this routine runs under the
* gConfigurationLock. The only time that this might not be the case
* is when the KEXT's start routine fails prior to gConfigurationLock
* being created.
*/
{
/* First prevent any more threads entering our listener. */
if (gListener != NULL) {
kauth_unlisten_scope(gListener);
gListener = NULL;
}
/* Then wait for any threads within out listener to stop. Note that there
* is still a race condition here; there could still be a thread executing
* between the OSDecrementAtomic and the return from the listener function
* (for example, FileOpScopeListener). However, there's no way to close
* this race because of the weak concurrency guarantee for kauth_unlisten_scope.
* Moreover, the window is very small and, seeing as this only happens during
* reconfiguration, I'm not too worried. However, I am worried enough
* to ensure that this loop runs at least once, so we always delay the teardown
* for at least one second waiting for the threads to drain from our
* listener.
*/
do {
struct timespec oneSecond;
oneSecond.tv_sec = 1;
oneSecond.tv_nsec = 0;
(void) msleep(&gActivationCount, NULL, PUSER, "com_apple_dts_kext_ClamAuth.RemoveListener", &oneSecond);
} while ( gActivationCount > 0 );
/* gListenerScope and gPrefix are both accessed by the listener callbacks
* without taking any form of lock. So, we don't destroy them until after
* all the listener callbacks have drained.
*/
if (gListenerScope != NULL) {
OSFree(gListenerScope, strlen(gListenerScope) + 1, gMallocTag);
gListenerScope = NULL;
}
gPrefix = NULL;
}
static void InstallListener(const char *scope, size_t scopeLen, const char *prefix)
/* Installs a listener for the specified scope. scope and scopeLen specifies
* the scope to listen for. prefix is a parameter for the scope listener.
* It may be NULL.
*
* prefix points into the gConfiguration global variable, so this routine
* doesn't make a copy of it. However, it has to make a copy of scope
* because scope can point to a place in the middle of the gConfiguration
* variable, so there's no guarantee it's null terminated (which we need it
* to be in order to call kauth_listen_scope.
*
* This routine always runs under the gConfigurationLock.
*/
{
kauth_scope_callback_t callback;
assert(scope != NULL);
assert(scopeLen > 0);
/* Allocate memory for the scope string. We need to keep a persistent
* copy of this string because kauth_listen_scope doesn't make a copy of
* its scope identifier input parameter. Normally you'd use a constant
* string, which persists as long as the kext is loaded, but I can't do
* that because the scope identifier is supplied by the user via sysctl.
*/
assert(gListenerScope == NULL);
gListenerScope = OSMalloc(scopeLen + 1, gMallocTag);
if (gListenerScope == NULL) {
printf("ClamAuth.InstallListener: Could not allocate gListenerScope.\n");
} else {
memcpy(gListenerScope, scope, scopeLen);
gListenerScope[scopeLen] = 0;
/* Copy the prefix pointer over to gPrefix. */
assert(gPrefix == NULL);
gPrefix = prefix;
/* Register the appropriate listener with Kauth. */
if ( strcmp(gListenerScope, KAUTH_SCOPE_GENERIC) == 0 ) {
callback = GenericScopeListener;
} else if ( strcmp(gListenerScope, KAUTH_SCOPE_PROCESS) == 0 ) {
callback = ProcessScopeListener;
} else if ( strcmp(gListenerScope, KAUTH_SCOPE_VNODE) == 0 ) {
callback = VnodeScopeListener;
} else if ( strcmp(gListenerScope, KAUTH_SCOPE_FILEOP) == 0 ) {
callback = FileOpScopeListener;
} else {
callback = UnknownScopeListener;
}
assert(gListener == NULL);
gListener = kauth_listen_scope(gListenerScope, callback, NULL);
if (gListener == NULL) {
printf("ClamAuth.InstallListener: Could not create gListener.\n");
}
}
/* In the event of any failure, call RemoveListener which will
* do all the right cleanup.
*/
if ( gListenerScope == NULL || gListener == NULL ) {
RemoveListener();
}
}
static void ConfigureKauth(const char *configuration)
/* This routine is called by the sysctl handler when it notices
* that the configuration has changed. It's responsible for
* parsing the new configuration string and updating the listener.
*
* See SysctlHandler for a description of how I chose to handle the
* failure case.
*
* This routine always runs under the gConfigurationLock.
*/
{
assert(configuration != NULL);
/* Remove the existing listener. */
RemoveListener();
/* Parse the configuration string and install the new listener. */
if (strcmp(configuration, "remove") == 0) {
printf("ClamAuth.ConfigureKauth: Removed listener.\n");
} else if ( strprefix(configuration, "add ") ) {
const char *cursor;
const char *scopeStart;
const char *scopeEnd;
const char *prefixStart;
/* Skip the "add ". */
cursor = configuration + strlen("add ");
/* Work out the span of the scope. */
scopeStart = cursor;
while ( (*cursor != ' ') && (*cursor != 0) ) {
cursor += 1;
}
scopeEnd = cursor;
if (scopeStart == scopeEnd) {
printf("ClamAuth.ConfigureKauth: Bad configuration '%s'.\n", configuration);
} else {
/* Look for a prefix. */
if (*cursor == ' ') {
cursor += 1;
}
if (*cursor == 0) {
prefixStart = NULL;
} else {
prefixStart = cursor;
}
/* Tell the user what we're doing. */
if (prefixStart == NULL) {
printf("ClamAuth.ConfigureKauth: scope = %.*s\n", (int) (scopeEnd - scopeStart), scopeStart);
} else {
printf("ClamAuth.ConfigureKauth: scope = %.*s, prefix = %s\n", (int) (scopeEnd - scopeStart), scopeStart, prefixStart);
}
InstallListener(scopeStart, scopeEnd - scopeStart, prefixStart);
}
} else {
printf("ClamAuth.ConfigureKauth: Bad configuration '%s'.\n", configuration);
}
}
/* gConfiguration holds our current configuration string. It's modified by
* SysctlHandler (well, by sysctl_handle_string which is called by SysctlHandler).
*/
static char gConfiguration[1024];
static int SysctlHandler(
struct sysctl_oid * oidp,
void * arg1,
int arg2,
struct sysctl_req * req
)
/* This routine is called by the kernel when the user reads or
* writes our sysctl variable. The arguments are standard for
* a sysctl handler.
*/
{
int result;
/* Prevent two threads trying to change our configuration at the same
* time.
*/
lck_mtx_lock(gConfigurationLock);
/* Let sysctl_handle_string do all the heavy lifting of getting
* and setting the variable.
*/
result = sysctl_handle_string(oidp, arg1, arg2, req);
/* On the way out, if we got no error and a new value was set,
* do our magic.
*/
if ( (result == 0) && (req->newptr != 0) ) {
ConfigureKauth(gConfiguration);
}
lck_mtx_unlock(gConfigurationLock);
return result;
}
/* Declare our sysctl OID (that is, a variable that the user can
* get and set using sysctl). Once this OID is registered (which
* is done in the start routine, ClamAuth_start, below), the user
* user can get and set our configuration variable (gConfiguration)
* using the sysctl command line tool.
*
* We use OID using SYSCTL_OID rather than SYSCTL_STRING because
* we want to override the hander function that's call (we want
* SysctlHandler rather than sysctl_handle_string).
*/
SYSCTL_OID(
_kern, /* parent OID */
OID_AUTO, /* sysctl number, OID_AUTO means we're only accessible by name */
com_apple_dts_kext_ClamAuth, /* our name */
CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_KERN, /* we're a string, more or less */
gConfiguration, /* sysctl_handle_string gets/sets this string */
sizeof(gConfiguration), /* and this is its maximum length */
SysctlHandler, /* our handler */
"A", /* because that's what SYSCTL_STRING does */
"" /* just a comment */
);
/* gRegisteredOID tracks whether we've registered our OID or not. */
static boolean_t gRegisteredOID = FALSE;
#pragma mark ***** Start/Stop
/* Prototypes for our entry points */
extern kern_return_t com_apple_dts_kext_ClamAuth_start(kmod_info_t * ki, void * d);
extern kern_return_t com_apple_dts_kext_ClamAuth_stop(kmod_info_t * ki, void * d);
extern kern_return_t com_apple_dts_kext_ClamAuth_start(kmod_info_t * ki, void * d)
/* Called by the system to start up the kext. */
{
#pragma unused(ki)
#pragma unused(d)
kern_return_t err;
/* Allocate our global resources, needed in order to allocate memory
* and locks throughout the rest of the program.
*/
err = KERN_SUCCESS;
gMallocTag = OSMalloc_Tagalloc("com.apple.dts.kext.ClamAuth", OSMT_DEFAULT);
if (gMallocTag == NULL) {
err = KERN_FAILURE;
}
if (err == KERN_SUCCESS) {
gLockGroup = lck_grp_alloc_init("com.apple.dts.kext.ClamAuth", LCK_GRP_ATTR_NULL);
if (gLockGroup == NULL) {
err = KERN_FAILURE;
}
}
/* Allocate the lock that protects our configuration. */
if (err == KERN_SUCCESS) {
gConfigurationLock = lck_mtx_alloc_init(gLockGroup, LCK_ATTR_NULL);
if (gConfigurationLock == NULL) {
err = KERN_FAILURE;
}
}
/* Register our sysctl handler. */
if (err == KERN_SUCCESS) {
sysctl_register_oid(&sysctl__kern_com_apple_dts_kext_ClamAuth);
gRegisteredOID = TRUE;
}
/* If we failed, shut everything down. */
if (err != KERN_SUCCESS) {
printf("ClamAuth_start: Failed to initialize the driver\n");
(void) com_apple_dts_kext_ClamAuth_stop(ki, d);
} else
printf("ClamAuth_start: ClamAV kernel driver loaded\n");
return err;
}
extern kern_return_t com_apple_dts_kext_ClamAuth_stop(kmod_info_t * ki, void * d)
/* Called by the system to shut down the kext. */
{
#pragma unused(ki)
#pragma unused(d)
/* Remove our sysctl handler. This prevents more threads entering the
* handler and trying to change the configuration. There is still a
* race condition here though. If a thread is already running in our
* sysctl handler, there's no way to guarantee that it's done before
* we destroy key resources (notably the gConfigurationLock mutex) that
* it depends on. That's because sysctl_unregister_oid makes no attempt
* to wait until all threads running inside the OID handler are done
* before it returns. I could do stuff to minimise the risk, but there's
* is no 100% way to close this race so I'm going to ignore it.
*/
if (gRegisteredOID) {
sysctl_unregister_oid(&sysctl__kern_com_apple_dts_kext_ClamAuth);
gRegisteredOID = FALSE;
}
/* Shut down the scope listen, if any. Not that we lock gConfigurationLock
* because RemoveListener requires it to be locked. Further note that
* we only do this if the lock has actually been allocated. If the startup
* routine fails, we can get called with gConfigurationLock set to NULL.
*/
if (gConfigurationLock != NULL) {
lck_mtx_lock(gConfigurationLock);
}
RemoveListener();
if (gConfigurationLock != NULL) {
lck_mtx_unlock(gConfigurationLock);
}
/* Clean up the configuration lock. */
if (gConfigurationLock != NULL) {
lck_mtx_free(gConfigurationLock, gLockGroup);
gConfigurationLock = NULL;
}
/* Clean up our global resources. */
if (gLockGroup != NULL) {
lck_grp_free(gLockGroup);
gLockGroup = NULL;
}
if (gMallocTag != NULL) {
OSMalloc_Tagfree(gMallocTag);
gMallocTag = NULL;
}
printf("ClamAuth_stop: ClamAV kernel driver removed\n");
return KERN_SUCCESS;
}