From 2b888381b047196ca5b18d2d644090acd1f716e5 Mon Sep 17 00:00:00 2001 From: Tomasz Kojm Date: Thu, 1 Dec 2011 19:09:58 +0100 Subject: [PATCH] OS X kernel driver --- contrib/ClamAuth/ClamAuth.c | 884 ++++++++++++++++++ .../ClamAuth.xcodeproj/project.pbxproj | 262 ++++++ contrib/ClamAuth/Info.plist | 29 + contrib/ClamAuth/LICENSE | 40 + 4 files changed, 1215 insertions(+) create mode 100644 contrib/ClamAuth/ClamAuth.c create mode 100644 contrib/ClamAuth/ClamAuth.xcodeproj/project.pbxproj create mode 100644 contrib/ClamAuth/Info.plist create mode 100644 contrib/ClamAuth/LICENSE diff --git a/contrib/ClamAuth/ClamAuth.c b/contrib/ClamAuth/ClamAuth.c new file mode 100644 index 000000000..25dd321e2 --- /dev/null +++ b/contrib/ClamAuth/ClamAuth.c @@ -0,0 +1,884 @@ +/* + * Copyright (c) 2007 by Apple Computer, Inc., All Rights Reserved. + * Copyright (c) 2011 Sourcefire, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 : "", + (dvpPath != NULL) ? dvpPath : "" + ); + } + } 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; +} diff --git a/contrib/ClamAuth/ClamAuth.xcodeproj/project.pbxproj b/contrib/ClamAuth/ClamAuth.xcodeproj/project.pbxproj new file mode 100644 index 000000000..8209b6cfd --- /dev/null +++ b/contrib/ClamAuth/ClamAuth.xcodeproj/project.pbxproj @@ -0,0 +1,262 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 52ADB27A148676E700208F0E /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 52ADB279148676E700208F0E /* LICENSE */; }; + 52ADB28014867AF400208F0E /* ClamAuth.c in Sources */ = {isa = PBXBuildFile; fileRef = 52ADB27F14867AF400208F0E /* ClamAuth.c */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 32A4FEC30562C75700D090E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 32A4FEC40562C75800D090E7 /* ClamAuth.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClamAuth.kext; sourceTree = BUILT_PRODUCTS_DIR; }; + 52ADB279148676E700208F0E /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 52ADB27F14867AF400208F0E /* ClamAuth.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ClamAuth.c; sourceTree = ""; }; + E4C7A4390832568C00556BCE /* Kernel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Kernel.framework; path = /System/Library/Frameworks/Kernel.framework; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 32A4FEBF0562C75700D090E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 089C166AFE841209C02AAC07 /* KauthORama */ = { + isa = PBXGroup; + children = ( + 52ADB27F14867AF400208F0E /* ClamAuth.c */, + 52ADB279148676E700208F0E /* LICENSE */, + 32A4FEC30562C75700D090E7 /* Info.plist */, + E4C7A4390832568C00556BCE /* Kernel.framework */, + 19C28FB6FE9D52B211CA2CBB /* Products */, + ); + name = KauthORama; + sourceTree = ""; + }; + 19C28FB6FE9D52B211CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 32A4FEC40562C75800D090E7 /* ClamAuth.kext */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 32A4FEBA0562C75700D090E7 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 32A4FEB80562C75700D090E7 /* ClamAuth */ = { + isa = PBXNativeTarget; + buildConfigurationList = E40CF0E60890089400F3BED8 /* Build configuration list for PBXNativeTarget "ClamAuth" */; + buildPhases = ( + 32A4FEB90562C75700D090E7 /* ShellScript */, + 32A4FEBA0562C75700D090E7 /* Headers */, + 32A4FEBB0562C75700D090E7 /* Resources */, + 32A4FEBD0562C75700D090E7 /* Sources */, + 32A4FEBF0562C75700D090E7 /* Frameworks */, + 32A4FEC00562C75700D090E7 /* Rez */, + 32A4FEC10562C75700D090E7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ClamAuth; + productInstallPath = "$(SYSTEM_LIBRARY_DIR)/Extensions"; + productName = KauthORama; + productReference = 32A4FEC40562C75800D090E7 /* ClamAuth.kext */; + productType = "com.apple.product-type.kernel-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 089C1669FE841209C02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0420; + }; + buildConfigurationList = E40CF0EA0890089400F3BED8 /* Build configuration list for PBXProject "ClamAuth" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + en, + ); + mainGroup = 089C166AFE841209C02AAC07 /* KauthORama */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 32A4FEB80562C75700D090E7 /* ClamAuth */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 32A4FEBB0562C75700D090E7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 52ADB27A148676E700208F0E /* LICENSE in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXRezBuildPhase section */ + 32A4FEC00562C75700D090E7 /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXRezBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 32A4FEB90562C75700D090E7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "script=\"${SYSTEM_DEVELOPER_DIR}/ProjectBuilder Extras/Kernel Extension Support/KEXTPreprocess\";\nif [ -x \"$script\" ]; then\n . \"$script\"\nfi"; + }; + 32A4FEC10562C75700D090E7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "script=\"${SYSTEM_DEVELOPER_DIR}/ProjectBuilder Extras/Kernel Extension Support/KEXTPostprocess\";\nif [ -x \"$script\" ]; then\n . \"$script\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 32A4FEBD0562C75700D090E7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 52ADB28014867AF400208F0E /* ClamAuth.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + E40CF0E70890089400F3BED8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = Info.plist; + MODULE_NAME = com.apple.dts.kext.ClamAuth; + MODULE_START = com_apple_dts_kext_ClamAuth_start; + MODULE_STOP = com_apple_dts_kext_ClamAuth_stop; + MODULE_VERSION = 0.1; + PRODUCT_NAME = ClamAuth; + WRAPPER_EXTENSION = kext; + }; + name = Debug; + }; + E40CF0E80890089400F3BED8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = Info.plist; + MODULE_NAME = com.apple.dts.kext.ClamAuth; + MODULE_START = com_apple_dts_kext_ClamAuth_start; + MODULE_STOP = com_apple_dts_kext_ClamAuth_stop; + MODULE_VERSION = 0.1; + PRODUCT_NAME = ClamAuth; + WRAPPER_EXTENSION = kext; + }; + name = Release; + }; + E40CF0EB0890089400F3BED8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VALUE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.7; + SDKROOT = macosx; + WARNING_CFLAGS = "-Wall"; + }; + name = Debug; + }; + E40CF0EC0890089400F3BED8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + COPY_PHASE_STRIP = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = s; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VALUE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.7; + SDKROOT = macosx; + WARNING_CFLAGS = "-Wall"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E40CF0E60890089400F3BED8 /* Build configuration list for PBXNativeTarget "ClamAuth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E40CF0E70890089400F3BED8 /* Debug */, + E40CF0E80890089400F3BED8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + E40CF0EA0890089400F3BED8 /* Build configuration list for PBXProject "ClamAuth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E40CF0EB0890089400F3BED8 /* Debug */, + E40CF0EC0890089400F3BED8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 089C1669FE841209C02AAC07 /* Project object */; +} diff --git a/contrib/ClamAuth/Info.plist b/contrib/ClamAuth/Info.plist new file mode 100644 index 000000000..c692a5917 --- /dev/null +++ b/contrib/ClamAuth/Info.plist @@ -0,0 +1,29 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ClamAuth + CFBundleIconFile + + CFBundleIdentifier + com.apple.dts.kext.ClamAuth + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + KEXT + CFBundleSignature + ???? + CFBundleVersion + $(MODULE_VERSION) + OSBundleLibraries + + com.apple.kpi.bsd + 8.0.0 + com.apple.kpi.libkern + 8.0.0 + + + diff --git a/contrib/ClamAuth/LICENSE b/contrib/ClamAuth/LICENSE new file mode 100644 index 000000000..034c34dc9 --- /dev/null +++ b/contrib/ClamAuth/LICENSE @@ -0,0 +1,40 @@ +Copyright (C) 2011 Sourcefire, Inc., All Rights Reserved. + +The KAuth code is based on KauthORama: + +Copyright (c) 2007 by Apple Computer, Inc., All Rights Reserved. + +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. + ("Apple") in consideration of your agreement to the following terms, and your + use, installation, modification or redistribution of this Apple software + constitutes acceptance of these terms. If you do not agree with these terms, + please do not use, install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and subject + to these terms, Apple grants you a personal, non-exclusive license, under Apple's + copyrights in this original Apple software (the "Apple Software"), to use, + reproduce, modify and redistribute the Apple Software, with or without + modifications, in source and/or binary forms; provided that if you redistribute + the Apple Software in its entirety and without modifications, you must retain + this notice and the following text and disclaimers in all such redistributions of + the Apple Software. Neither the name, trademarks, service marks or logos of + Apple Computer, Inc. may be used to endorse or promote products derived from the + Apple Software without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or implied, + are granted by Apple herein, including but not limited to any patent rights that + may be infringed by your derivative works or by other works in which the Apple + Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO + WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED + WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN + COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION + OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT + (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file