/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
/*
* Threads:
*
* auditd is thread 0 and does signal handling
*
* input() is a door server that receives binary audit records and
* queues them for handling by an instance of process() for conversion to syslog
* message(s). There is one process thread per plugin.
*
* Queues:
*
* Each plugin has a buffer pool and and queue for feeding the
* the process threads. The input thread moves buffers from the pool
* to the queue and the process thread puts them back.
*
* Another pool, b_pool, contains buffers referenced by each of the
* process queues; this is to minimize the number of buffer copies
*
*/
#include <assert.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <libintl.h>
#include <pthread.h>
#include <secdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <audit_plugin.h> /* libbsm */
#include "plugin.h"
#include <bsm/audit_door_infc.h>
#include "queue.h"
#define DEBUG 0
/* gettext() obfuscation routine for lint */
#ifdef __lint
#define gettext(x) x
#endif
#if DEBUG
#else
#define DUMP(w, x, y, z)
#define DPRINT(x)
#endif
static int b_allocated = 0;
static void input(void *, void *, int, door_desc_t *, int);
static void qpool_init(plugin_t *, int);
static void qpool_close(plugin_t *);
static void bpool_init();
static void bpool_return(audit_rec_t *);
/*
* warn_or_fatal() -- log daemon error and (optionally) exit
*/
static void
{
char *severity;
if (fatal)
else
if (fatal)
auditd_exit(1);
}
/* Internal to doorway.c errors... */
/*
* report_error -- handle errors returned by plugin
*
* rc is plugin's return code if it is a non-negative value,
* otherwise it is a doorway.c code about a plugin.
*/
static void
{
int warn = 0;
int bad_count = 0;
char *name;
static int no_plug = 0;
static int no_load = 0;
static int no_thread;
static int no_memory = 0;
static int invalid = 0;
static int retry = 0;
static int fail = 0;
name = plugin_path;
if (error_text == NULL)
error_text = empty;
switch (rc) {
case INTERNAL_LOAD_ERROR:
warn = 1;
break;
case INTERNAL_SYS_ERROR:
warn = 1;
break;
case INTERNAL_CONFIG_ERROR:
warn = 1;
break;
case AUDITD_SUCCESS:
break;
case AUDITD_NO_MEMORY: /* no_memory */
warn = 1;
break;
case AUDITD_INVALID: /* invalid */
warn = 1;
break;
case AUDITD_RETRY:
warn = 1;
break;
case AUDITD_COMM_FAIL: /* comm_fail */
break;
case AUDITD_FATAL: /* failure */
warn = 1;
break;
default:
break;
}
if (warn)
else {
gettext("audit plugin %s reported error = \"%s\": %s\n"),
warn_or_fatal(0, message);
}
}
static size_t
{
char tokenid;
(tokenid == AUT_HEADER64_EX)) {
return (len);
}
return (0);
}
/*
* load_function - call dlsym() to resolve the function address
*/
static int
{
gettext("dlsym failed %s: error %s"),
warn_or_fatal(0, message);
return (-1);
}
return (0);
}
/*
* load the auditd plug in
*/
static int
{
int fd;
int fail = 0;
/*
* Stat the file so we can check modes and ownerships
*/
fail = 1;
} else
fail = 1;
if (fail) {
gettext("auditd plugin: stat(%s) failed: %s\n"),
warn_or_fatal(0, message);
return (-1);
}
/*
* Check the ownership of the file
*/
"auditd plugin: Owner of the module %s is not root\n"),
p->plg_path);
warn_or_fatal(0, message);
return (-1);
}
/*
* Check the modes on the file
*/
gettext("auditd plugin: module %s writable by group\n"),
p->plg_path);
warn_or_fatal(0, message);
return (-1);
}
gettext("auditd plugin: module %s writable by world\n"),
p->plg_path);
warn_or_fatal(0, message);
return (-1);
}
/*
* Open the plugin
*/
gettext("plugin load %s failed: %s\n"),
gettext("Unknown error\n"));
warn_or_fatal(0, message);
return (-1);
}
return (-1);
return (-1);
return (-1);
return (0);
}
/*
* unload_plugin() unlinks and frees the plugin_t structure after
* freeing buffers and structures that hang off it. It also dlcloses
* the referenced plugin. The return is the next entry, which may be NULL
*
* hold plugin_mutex for this call
*/
static plugin_t *
{
plugin_t *q, **r;
qpool_close(p); /* qpool_close accepts NULL pool, queue */
(void) pthread_mutex_destroy(&(p->plg_mutex));
(void) pthread_cond_destroy(&(p->plg_cv));
q = plugin_head;
r = &plugin_head;
while (q != NULL) {
if (q == p) {
*r = p->plg_next;
free(p);
break;
}
r = &(q->plg_next);
q = q->plg_next;
}
return (*r);
}
/*
* process return values from plugin_open
*
* presently no attribute is defined.
*/
/* ARGSUSED */
static void
{
}
/*
* auditd_thread_init
* - create threads
* - load plugins
*
* auditd_thread_init is called at auditd startup with an initial list
* of plugins and again each time audit catches a SIGHUP or SIGUSR1.
*/
int
{
int threshold;
plugin_t *p;
char *open_params;
char *error_string;
int plugin_count = 0;
static int threads_ready = 0;
if (!threads_ready) {
#if DEBUG
#endif
if (doorfd < 0)
return (1); /* can't create door -> fatal */
¶m);
/* input door server */
in_thr.thd_waiting = 0;
bpool_init();
}
p = plugin_head;
while (p != NULL) {
if (p->plg_removed) {
/* tell process(p) to exit and dlclose */
(void) pthread_cond_signal(&(p->plg_cv));
} else if (!p->plg_initialized) {
p->plg_path));
if (load_plugin(p)) {
gettext("dynamic load failed"),
p->plg_path);
p = unload_plugin(p);
continue;
}
open_params = NULL;
error_string = NULL;
if ((rc = p->plg_fplugin_open(
p->plg_kvlist,
p = unload_plugin(p);
continue;
}
open_return(p, open_params);
p->plg_reopen = 0;
"calling qpool_init for %s with qmax=%d\n",
qpool_init(p, threshold);
audit_queue_init(&(p->plg_queue));
p->plg_initialized = 1;
p->plg_waiting = 0;
(void *(*)(void *))process, p)) {
gettext("thread creation failed"),
p->plg_path);
p = unload_plugin(p);
continue;
}
} else if (p->plg_reopen) {
error_string = NULL;
p = unload_plugin(p);
continue;
}
open_return(p, open_params);
p->plg_reopen = 0;
}
p = p->plg_next;
plugin_count++;
}
if (plugin_count == 0) {
return (-1);
}
if (!threads_ready) {
/* unleash the kernel */
rc));
if (rc != 0)
return (1); /* fatal */
threads_ready = 1;
}
return (0);
}
/*
* Door invocations that are in progress during a
* door_revoke() invocation are allowed to complete normally.
* -- man page for door_revoke()
*/
void
{
if (doorfd == -1)
return;
(void) door_revoke(doorfd);
doorfd = -1;
}
/*
* qpool_init() sets up pool for queue entries (audit_q_t)
*
*/
static void
{
int i;
audit_queue_init(&(p->plg_pool));
if (p->plg_qmax > largest_queue)
largest_queue = p->plg_qmax;
p->plg_q_threshold = threshold;
for (i = 0; i < p->plg_qmin; i++) {
/* doesn't return */
}
}
/*
* bpool_init() sets up pool and queue for record entries (audit_rec_t)
*
*/
static void
{
int i;
for (i = 0; i < INPUT_MIN; i++) {
/* doesn't return */
node->abq_data_len = 0;
(void) pthread_mutex_lock(&b_alloc_lock);
b_allocated++;
(void) pthread_mutex_unlock(&b_alloc_lock);
}
}
/*
* qpool_close() discard queue and pool for a discontinued plugin
*
* there is no corresponding bpool_close() since it would only
* be called as auditd is going down.
*/
static void
if (!p->plg_initialized)
return;
}
audit_queue_destroy(&(p->plg_pool));
}
audit_queue_destroy(&(p->plg_queue));
}
/*
* qpool_withdraw
*/
static audit_q_t *
{
int rc;
/* get a buffer from the pool, if any */
if (rc == 0)
return (node);
/*
* the pool is empty: allocate a new element
*/
/* doesn't return */
return (node);
}
/*
* bpool_withdraw -- gets a buffer and fills it
*
*/
static audit_rec_t *
{
int rc;
/* get a buffer from the pool, if any */
" requested size=%d, dequeue rc=%d\n",
if (rc == 0) {
/* no return */
}
} else {
/*
* the pool is empty: allocate a new element
*/
(void) pthread_mutex_lock(&b_alloc_lock);
if (b_allocated >= largest_queue) {
(void) pthread_mutex_unlock(&b_alloc_lock);
audit_queue_size(&b_pool)));
return (NULL);
}
(void) pthread_mutex_unlock(&b_alloc_lock);
/* no return */
(void) pthread_mutex_lock(&b_alloc_lock);
b_allocated++;
(void) pthread_mutex_unlock(&b_alloc_lock);
}
node->abq_ref_count = 0;
return (node);
}
/*
* qpool_return() moves queue nodes back to the pool queue.
*
* if the pool is over max, the node is discarded instead.
*/
static void
{
int qpool_size;
int q_size;
#if DEBUG
#endif
else
"qpool_return(%d): seq=%llu, q size=%d,"
" pool size=%d (total alloc=%d), threshhold=%d\n",
}
/*
* bpool_return() moves queue nodes back to the pool queue.
*/
static void
{
#if DEBUG
#endif
"bpool_return: requeue %p (allocated=%d,"
audit_queue_size(&b_pool)));
}
#if DEBUG
else {
"bpool_return: decrement count for %p (allocated=%d,"
audit_queue_size(&b_pool)));
}
#endif
}
#if DEBUG
static void
{
int policy;
/*
* count is message sequence
*/
" input_in_wait=%d"
" priority=%d"
" queue size=%d pool size=%d"
"\n\t"
"process wait=%d"
" tossed=%d"
" queued=%d"
" written=%d"
"\n",
audit_queue_size(&(p->plg_queue)),
audit_queue_size(&(p->plg_pool)),
p->plg_waiting, p->plg_tossed,
p->plg_queued, p->plg_output);
}
#endif
/*
* policy_is_block: return 1 if the continue policy is off for any active
* plugin, else 0
*/
static int
{
plugin_t *p;
(void) pthread_mutex_lock(&plugin_mutex);
p = plugin_head;
while (p != NULL) {
if (p->plg_cnt == 0) {
(void) pthread_mutex_unlock(&plugin_mutex);
"policy_is_block: policy is to block\n"));
return (1);
}
p = p->plg_next;
}
(void) pthread_mutex_unlock(&plugin_mutex);
return (0);
}
/*
* policy_update() -- the kernel has received a policy change.
* Presently, the only policy auditd cares about is AUDIT_CNT
*/
static void
{
plugin_t *p;
(void) pthread_mutex_lock(&plugin_mutex);
p = plugin_head;
while (p != NULL) {
(void) pthread_cond_signal(&(p->plg_cv));
p = p->plg_next;
}
(void) pthread_mutex_unlock(&plugin_mutex);
}
/*
* queue_buffer() inputs a buffer and queues for each active plugin if
* it represents a complete audit record. Otherwise it builds a
* larger buffer to hold the record and take successive buffers from
* c2audit to build a complete record; then queues it for each plugin.
*
* return 0 if data is queued (or damaged and tossed). If resources
* are not available, return 0 if all active plugins have the cnt
* policy set, else 1. 0 is also returned if the input is a control
* message. (aub_buf is aligned on a 64 bit boundary, so casting
* it to an integer works just fine.)
*/
static int
{
plugin_t *p;
/*
* the buffer may be a kernel -> auditd message. (only
* the policy change message exists so far.)
*/
switch (control) {
case AU_DBUF_POLICY:
/* LINTED */
break;
case AU_DBUF_SHUTDOWN:
break;
default:
break;
}
return (0);
}
/*
* The test for valid continuation/completion may fail. Need to
* assume the failure was earlier and that this buffer may
* be a valid first or complete buffer after discarding the
* incomplete record
*/
if (alt_b_copy != NULL) {
alt_b_copy = NULL;
}
}
alt_b_copy = NULL;
return (0);
}
return (0);
b_copy = alt_b_copy;
alt_b_copy = NULL;
/* first buffer of a multiple buffer record */
if ((alt_length < MIN_RECORD_SIZE) ||
return (0);
}
if (alt_b_copy == NULL)
return (policy_is_block());
return (0);
} else { /* one buffer, one record -- the basic case */
return (0); /* tossed */
}
return (policy_is_block());
}
(void) pthread_mutex_lock(&plugin_mutex);
p = plugin_head;
while (p != NULL) {
if (!p->plg_removed) {
/*
* Link the record buffer to the input queues.
* To avoid a race, it is necessary to wait
* until all reference count increments
* are complete before queueing q_copy.
*/
q_copy = qpool_withdraw(p);
referenced = 1;
} else
p->plg_save_q_copy = NULL;
p = p->plg_next;
}
/*
* now that the reference count is updated, queue it.
*/
if (referenced) {
p = plugin_head;
(void) pthread_cond_signal(&(p->plg_cv));
p->plg_queued++;
p = p->plg_next;
}
} else
(void) pthread_mutex_unlock(&plugin_mutex);
return (0);
}
/*
* wait_a_while() -- timed wait in the door server to allow output
* time to catch up.
*/
static void
{
in_thr.thd_waiting = 0;
}
/*
* adjust_priority() -- check queue and pools and adjust the priority
* for process() accordingly. If we're way ahead of output, do a
* timed wait as well.
*/
static void
{
int queue_near_full;
plugin_t *p;
int queue_size;
queue_near_full = 0;
(void) pthread_mutex_lock(&plugin_mutex);
p = plugin_head;
while (p != NULL) {
if (queue_size > p->plg_q_threshold) {
if (p->plg_priority != HIGH_PRIORITY) {
p->plg_priority =
(void) pthread_setschedparam(p->plg_tid,
SCHED_OTHER, ¶m);
}
queue_near_full = 1;
break;
}
}
p = p->plg_next;
}
(void) pthread_mutex_unlock(&plugin_mutex);
if (queue_near_full) {
"adjust_priority: input taking a short break\n"));
wait_a_while();
"adjust_priority: input back from my break\n"));
}
}
/*
* input() is a door server; it blocks if any plugins have full queues
* with the continue policy off. (auditconfig -setpolicy -cnt)
*
* input() is called synchronously from c2audit and is NOT
* reentrant due to the (unprotected) static variables in
* queue_buffer(). If multiple clients are created, a context
* structure will be required for queue_buffer.
*
* timedwait is used when input() gets too far ahead of process();
* the wait terminates either when the set time expires or when
* process() signals that it has nearly caught up.
*/
/* ARGSUSED */
static void
int n_descriptors)
{
int is_blocked;
plugin_t *p;
#if DEBUG
int loop_count = 0;
static int call_counter = 0;
#endif
gettext("invalid data received from c2audit\n"));
goto input_exit;
}
"partial=%u, arg_size=%d\n",
gettext("invalid data length received from c2audit\n"));
goto input_exit;
}
/*
* is_blocked is true only if one or more plugins have "no
* continue" (-cnt) set and one of those has a full queue.
* All plugins block until success is met.
*/
for (;;) {
call_counter));
if (!is_blocked) {
break;
} else {
"%d input blocked (loop=%d)\n",
wait_a_while();
}
#if DEBUG
loop_count++;
#endif
}
p = plugin_head;
while (p != NULL) {
(void) pthread_cond_signal(&(p->plg_cv));
p = p->plg_next;
}
}
/*
* process() -- pass a buffer to a plugin
*/
static void
{
int rc;
char *error_string;
int sendsignal;
int queue_len;
for (;;) {
(void) pthread_mutex_lock(&(p->plg_mutex));
p->plg_waiting++;
(void) pthread_cond_wait(&(p->plg_cv),
&(p->plg_mutex));
p->plg_waiting--;
(void) pthread_mutex_unlock(&(p->plg_mutex));
if (p->plg_removed)
goto plugin_removed;
}
#if DEBUG
"process(%d): buffer sequence=%llu but prev=%llu\n",
p->plg_last_seq_out);
#endif
error_string = NULL;
if (p->plg_removed)
goto plugin_removed;
#if DEBUG
#endif
switch (plugrc) {
case AUDITD_RETRY:
if (!once) {
}
error_string = NULL;
" cnt=%d (if 1, enter retry)\n",
if (p->plg_cnt) /* if cnt is on, lose the buffer */
break;
(void) pthread_mutex_lock(&(p->plg_mutex));
p->plg_waiting++;
(void) pthread_cond_reltimedwait_np(&(p->plg_cv),
p->plg_waiting--;
(void) pthread_mutex_unlock(&(p->plg_mutex));
goto retry_mode;
case AUDITD_SUCCESS:
p->plg_output++;
break;
default:
error_string = NULL;
break;
} /* end switch */
qpool_return(p, q_node);
sendsignal = 0;
(queue_len < p->plg_q_threshold))
sendsignal = 1;
if (sendsignal) {
/*
* sched_yield(); does not help
* performance and in artificial tests
* (high sustained volume) appears to
* hurt by adding wide variability in
* the results.
*/
} else if ((p->plg_priority < BASE_PRIORITY) &&
(queue_len < p->plg_q_threshold)) {
¶m);
}
} /* end for (;;) */
error_string = NULL;
(void) pthread_mutex_lock(&plugin_mutex);
(void) unload_plugin(p);
(void) pthread_mutex_unlock(&plugin_mutex);
}