mirror of https://github.com/Cisco-Talos/clamav
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.
770 lines
24 KiB
770 lines
24 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/systm.h>
|
|
#include <sys/kauth.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/conf.h>
|
|
#include <miscfs/devfs/devfs.h>
|
|
|
|
#define CLAMAUTH_VERSION "0.3"
|
|
#define CLAMAUTH_PROTOCOL_VERSION 2
|
|
|
|
#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;
|
|
|
|
#define CLAMAUTH_EVENTS (KAUTH_VNODE_EXECUTE)
|
|
|
|
struct AuthEvent {
|
|
/* don't change the first two fields */
|
|
UInt32 action;
|
|
char path[1024];
|
|
UInt32 pid;
|
|
|
|
};
|
|
|
|
#define EVENTQSIZE 64
|
|
|
|
struct AuthEventQueue {
|
|
struct AuthEvent queue[EVENTQSIZE];
|
|
int cnt, first, last;
|
|
};
|
|
|
|
void AuthEventInitQueue(struct AuthEventQueue *queue);
|
|
void AuthEventEnqueue(struct AuthEventQueue *queue, struct AuthEvent *event);
|
|
int AuthEventDequeue(struct AuthEventQueue *queue, struct AuthEvent *event);
|
|
|
|
void AuthEventInitQueue(struct AuthEventQueue *queue)
|
|
{
|
|
memset(queue, 0, sizeof(struct AuthEventQueue));
|
|
queue->first = queue->cnt = 0;
|
|
queue->last = EVENTQSIZE - 1;
|
|
}
|
|
|
|
void AuthEventEnqueue(struct AuthEventQueue *queue, struct AuthEvent *event)
|
|
{
|
|
queue->last = (queue->last + 1) % EVENTQSIZE;
|
|
memcpy(&queue->queue[queue->last], event, sizeof(struct AuthEvent));
|
|
queue->cnt++;
|
|
}
|
|
|
|
int AuthEventDequeue(struct AuthEventQueue *queue, struct AuthEvent *event)
|
|
{
|
|
if(!queue->cnt)
|
|
return 1;
|
|
memcpy(event, &queue->queue[queue->first], sizeof(struct AuthEvent));
|
|
queue->first = (queue->first + 1) % EVENTQSIZE;
|
|
queue->cnt--;
|
|
return 0;
|
|
}
|
|
|
|
struct AuthEventQueue gEventQueue;
|
|
static lck_mtx_t *gEventQueueLock = NULL;
|
|
static SInt32 gEventCount = 0;
|
|
|
|
#define MAX_PREFIX_NUM 10
|
|
#define MAX_PREFIX_LEN 128
|
|
static char gPrefixTable[MAX_PREFIX_NUM][MAX_PREFIX_LEN];
|
|
static unsigned int gPrefixCount = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
/* /dev/clamauth handling */
|
|
|
|
static int ca_devidx = -1;
|
|
static void *ca_devnode = NULL;
|
|
int dev_open = 0, dev_read = 0;
|
|
|
|
static int ca_open(dev_t dev, int flag, int devtype, proc_t p)
|
|
{
|
|
if(dev_open)
|
|
return EBUSY;
|
|
|
|
dev_open = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ca_close(dev_t dev, int flag, int devtype, proc_t p)
|
|
{
|
|
struct AuthEvent event;
|
|
|
|
lck_mtx_lock(gEventQueueLock);
|
|
dev_open = 0;
|
|
dev_read = 0;
|
|
AuthEventInitQueue(&gEventQueue);
|
|
/* Initialize event queue and add version info event */
|
|
event.action = CLAMAUTH_PROTOCOL_VERSION;
|
|
strncpy(event.path, "ClamAuth "CLAMAUTH_VERSION"", sizeof(event.path));
|
|
event.pid = 0xdeadbeef;
|
|
AuthEventEnqueue(&gEventQueue, &event);
|
|
lck_mtx_unlock(gEventQueueLock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ca_read(dev_t dev, uio_t uio, int ioflag)
|
|
{
|
|
int ret = 0, size, retq = 0;
|
|
struct AuthEvent event;
|
|
struct timespec waittime;
|
|
|
|
waittime.tv_sec = 1;
|
|
waittime.tv_nsec = 0;
|
|
while(uio_resid(uio) > 0) {
|
|
lck_mtx_lock(gEventQueueLock);
|
|
retq = AuthEventDequeue(&gEventQueue, &event);
|
|
dev_read = 1;
|
|
lck_mtx_unlock(gEventQueueLock);
|
|
if(retq != 1) {
|
|
/* snprintf(info, sizeof(info), "PATH: %s, PID: %d, ACTION: %d\n", event.path, event.pid, event.action); */
|
|
size = MIN(uio_resid(uio), sizeof(event));
|
|
ret = uiomove((const char *) &event, size, uio);
|
|
if(ret)
|
|
break;
|
|
} else {
|
|
//(void) msleep(&gEventQueue, NULL, PUSER, "events", &waittime);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(ret) {
|
|
printf("ClamAuth: uiomove() failed\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ca_write(dev_t dev, uio_t uio, int ioflag)
|
|
{
|
|
return EBADF;
|
|
}
|
|
|
|
static int ca_ioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, proc_t p)
|
|
{
|
|
return EBADF;
|
|
}
|
|
|
|
static int ca_select(dev_t dev, int flag, void * wql, proc_t p)
|
|
{
|
|
return EBADF;
|
|
}
|
|
|
|
static struct cdevsw clamauth_cdevsw = {
|
|
ca_open,
|
|
ca_close,
|
|
ca_read,
|
|
ca_write,
|
|
ca_ioctl,
|
|
eno_stop,
|
|
eno_reset,
|
|
NULL,
|
|
ca_select,
|
|
eno_mmap,
|
|
eno_strat,
|
|
eno_getc,
|
|
eno_putc,
|
|
0
|
|
};
|
|
|
|
static int ca_remove(void)
|
|
{
|
|
if(ca_devnode)
|
|
devfs_remove(ca_devnode);
|
|
|
|
if(ca_devidx != -1) {
|
|
if(cdevsw_remove(ca_devidx, &clamauth_cdevsw) != ca_devidx) {
|
|
printf("ClamAuth: cdevsw_remove() failed\n");
|
|
return KERN_FAILURE;
|
|
}
|
|
}
|
|
|
|
return KERN_SUCCESS;
|
|
}
|
|
|
|
|
|
#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).
|
|
* 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 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;
|
|
struct AuthEvent event;
|
|
unsigned int i, mpath = 0;
|
|
|
|
(void) OSIncrementAtomic(&gActivationCount);
|
|
|
|
context = (vfs_context_t) arg0;
|
|
vp = (vnode_t) arg1;
|
|
dvp = (vnode_t) arg2;
|
|
|
|
vpPath = NULL;
|
|
dvpPath = 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);
|
|
|
|
/* 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) {
|
|
for(i = 0; i < gPrefixCount; i++) {
|
|
if(vpPath && strprefix(vpPath, gPrefixTable[i])) {
|
|
mpath = 1;
|
|
} else if(dvpPath && strprefix(dvpPath, gPrefixTable[i])) {
|
|
mpath = 1;
|
|
}
|
|
if(mpath)
|
|
break;
|
|
}
|
|
|
|
if (mpath) {
|
|
if(action & CLAMAUTH_EVENTS)
|
|
printf(
|
|
"scope=" KAUTH_SCOPE_VNODE ", uid=%ld, vp=%s, dvp=%s\n",
|
|
(long) kauth_cred_getuid(vfs_context_ucred(context)),
|
|
(vpPath != NULL) ? vpPath : "<null>",
|
|
(dvpPath != NULL) ? dvpPath : "<null>"
|
|
);
|
|
|
|
event.pid = vfs_context_pid(context);
|
|
event.action = action;
|
|
if(vpPath) {
|
|
strncpy(event.path, vpPath, sizeof(event.path));
|
|
event.path[sizeof(event.path) - 1] = 0;
|
|
} else {
|
|
event.path[0] = 0;
|
|
}
|
|
lck_mtx_lock(gEventQueueLock);
|
|
if(dev_read && (action & CLAMAUTH_EVENTS)) {
|
|
// printf("gPrefix: %s, vpPath: %s, dvpPath: %s, action: %d\n", gPrefix, vpPath ? vpPath : "<null>", dvpPath ? dvpPath : "<null>", action);
|
|
AuthEventEnqueue(&gEventQueue, &event);
|
|
}
|
|
lck_mtx_unlock(gEventQueueLock);
|
|
(void) OSIncrementAtomic(&gEventCount);
|
|
}
|
|
} else {
|
|
printf("ClamAuth.VnodeScopeListener: Error %d.\n", err);
|
|
}
|
|
|
|
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(credential)
|
|
#pragma unused(idata)
|
|
#pragma unused(arg2)
|
|
#pragma unused(arg3)
|
|
struct AuthEvent event;
|
|
vfs_context_t context;
|
|
const char *path;
|
|
unsigned int i, mpath = 0;
|
|
|
|
if(!dev_read)
|
|
return KAUTH_RESULT_DEFER;
|
|
|
|
context = (vfs_context_t) arg0;
|
|
path = (const char *) arg1;
|
|
|
|
(void) OSIncrementAtomic(&gActivationCount);
|
|
|
|
switch (action) {
|
|
/* case KAUTH_FILEOP_OPEN: */
|
|
case KAUTH_FILEOP_EXEC:
|
|
for(i = 0; i < gPrefixCount; i++) {
|
|
if(strprefix((const char *) arg1, gPrefixTable[i])) {
|
|
mpath = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(mpath) {
|
|
event.pid = vfs_context_pid(context);
|
|
event.action = action;
|
|
strncpy(event.path, path, sizeof(event.path));
|
|
event.path[sizeof(event.path) - 1] = 0;
|
|
lck_mtx_lock(gEventQueueLock);
|
|
if(dev_read)
|
|
AuthEventEnqueue(&gEventQueue, &event);
|
|
lck_mtx_unlock(gEventQueueLock);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
(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 );
|
|
}
|
|
|
|
static void InstallListener(void)
|
|
/* 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.
|
|
*/
|
|
{
|
|
|
|
assert(gListener == NULL);
|
|
//gListener = kauth_listen_scope(KAUTH_SCOPE_VNODE, VnodeScopeListener, NULL);
|
|
gListener = kauth_listen_scope(KAUTH_SCOPE_FILEOP, FileOpScopeListener, NULL);
|
|
if (gListener == NULL) {
|
|
printf("ClamAuth.InstallListener: Could not create gListener.\n");
|
|
RemoveListener();
|
|
} else {
|
|
printf("ClamAuth: Installed file listener\n");
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
{
|
|
unsigned int i = 0;
|
|
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, "monitor ") ) {
|
|
const char *cursor;
|
|
|
|
/* Skip the "monitor ". */
|
|
cursor = configuration + strlen("monitor ");
|
|
|
|
gPrefixCount = 0;
|
|
while(*cursor == ' ')
|
|
cursor++;
|
|
|
|
if (!*cursor) {
|
|
printf("ClamAuth.ConfigureKauth: Bad configuration '%s'.\n", configuration);
|
|
return;
|
|
}
|
|
|
|
while(1) {
|
|
if(i < MAX_PREFIX_LEN - 1) {
|
|
if(*cursor == ' ') {
|
|
gPrefixTable[gPrefixCount][i] = 0;
|
|
gPrefixCount++;
|
|
i = 0;
|
|
if(gPrefixCount >= MAX_PREFIX_NUM) {
|
|
printf("ClamAuth.ConfigureKauth: Too many paths (> %u).\n", MAX_PREFIX_NUM);
|
|
gPrefixCount = 0;
|
|
return;
|
|
}
|
|
} else {
|
|
gPrefixTable[gPrefixCount][i++] = *cursor;
|
|
}
|
|
} else {
|
|
printf("ClamAuth.ConfigureKauth: Path too long (%u > %u).\n", i, MAX_PREFIX_LEN);
|
|
gPrefixCount = 0;
|
|
return;
|
|
}
|
|
cursor++;
|
|
if(!*cursor) {
|
|
gPrefixTable[gPrefixCount][i] = 0;
|
|
gPrefixCount++;
|
|
break;
|
|
}
|
|
}
|
|
printf("ClamAuth.ConfigureKauth: Monitoring %u path(s)\n", gPrefixCount);
|
|
InstallListener();
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
struct AuthEvent event;
|
|
|
|
ca_devidx = cdevsw_add(-1, &clamauth_cdevsw);
|
|
if(ca_devidx == -1) {
|
|
printf("ClamAuth: cdevsw_add() failed\n");
|
|
return KERN_FAILURE;
|
|
}
|
|
|
|
ca_devnode = devfs_make_node(makedev(ca_devidx, 0), DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0660, "clamauth");
|
|
if(!ca_devnode) {
|
|
printf("ClamAuth: Can't create /dev/clamauth\n");
|
|
return ca_remove();
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/* Event queue lock */
|
|
if (err == KERN_SUCCESS) {
|
|
gEventQueueLock = lck_mtx_alloc_init(gLockGroup, LCK_ATTR_NULL);
|
|
if (gEventQueueLock == NULL) {
|
|
err = KERN_FAILURE;
|
|
}
|
|
}
|
|
AuthEventInitQueue(&gEventQueue);
|
|
|
|
/* Initialize event queue and add version info event */
|
|
event.action = CLAMAUTH_PROTOCOL_VERSION;
|
|
strncpy(event.path, "ClamAuth "CLAMAUTH_VERSION"", sizeof(event.path));
|
|
event.pid = 0xdeadbeef;
|
|
AuthEventEnqueue(&gEventQueue, &event);
|
|
|
|
/* 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)
|
|
int ret;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* remove the character device */
|
|
ret = ca_remove();
|
|
|
|
/* 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 the event queue lock. */
|
|
if (gEventQueueLock != NULL) {
|
|
lck_mtx_free(gEventQueueLock, gLockGroup);
|
|
gEventQueueLock = 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 ret;
|
|
}
|
|
|