audit_io.c revision 9e9e6ab82d4247028c312ff50a65b8a05a194b33
/*
* 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
*/
/*
* Routines for writing audit records.
*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/sysmacros.h>
#include <c2/audit_kernel.h>
#include <c2/audit_record.h>
#include <c2/audit_kevents.h>
#include <c2/audit_door_infc.h>
static void audit_async_finish_backend(void *);
static int audit_sync_block(au_kcontext_t *);
/*
* each of these two tables are indexed by the values AU_DBUF_COMPLETE
* through AU_DBUF_LAST; the content is the next state value. The
* first table determines the next state for a buffer which is not the
* end of a record and the second table determines the state for a
* buffer which is the end of a record. The initial state is
* AU_DBUF_COMPLETE.
*/
static int state_if_part[] = {
static int state_if_not_part[] = {
/*
* Write to an audit descriptor.
* Add the au_membuf to the descriptor chain and free the chain passed in.
*/
void
au_uwrite(m)
token_t *m;
{
}
void
{
if (d == NULL) {
au_toss_token(m);
return;
}
if (m == (token_t *)0) {
printf("au_write: null token\n");
return;
}
if (*d == NULL)
*d = (caddr_t)m;
else
}
#define AU_INTERVAL 120
/*
* Write audit information to the disk.
* Called from auditsvc(); EOL'd as of Sol 10.
* Local zones are not allowed; the caller (auditsvc()) enforces the
* restriction.
*/
int
int limit;
{ /* AU_DOIO */
char *bp; /* start of free space in staging buffer */
unsigned char *cp; /* ptr to data to be moved */
/*
* size (data left in au_membuf - space in buffer)
*/
int error; /* error return */
int part = 0; /* partial audit record written */
int partial = 0; /* flag to force partial AR to file */
/* 0 - idle, ignore */
/* 1 - force write of audit record */
/* 2 - finished writing AR, commit */
kctx = GET_KCTX_GZ;
/*
* Check to ensure enough free space on audit device.
*/
/*
* Large Files: We do not convert any of this part of kernel
* to be large file aware. Original behaviour should be
* maintained. This function is called from audit_svc and
* it already checks for negative values of limit.
*/
return (ENOSPC);
return (EFBIG);
/*
* has the write buffer changed length due to a auditctl(2)?
* (remember that auk_buffer is an element of auk_dbuffer)
*/
/* bad, should not sleep here. Testing only */
}
goto nodata;
}
off = 0; /* no space used in buffer */
used = 0; /* no data processed in au_membuf */
while (cMB) {
/* indicate audit record being processed */
part = 1;
/* pointer to buffer data */
/* data left in au_membuf */
/* len to move */
/* move the data */
/* advance to next au_membuf */
used = 0;
}
/* advance to next AR */
/* reached end of an audit record */
part = 0;
/* force abort at end of audit record? */
if (partial == 1)
partial = 2;
}
/*
* If we've reached end of buffer, or have run out of
* audit records on the queue or we've processed a
* partial audit record to complete the audit file,
* then its time to flush the holding buffer to the
* audit trail.
*/
(partial == 2)) {
left = 0;
/*
* Largefiles: We purposely pass a value of
* MAXOFF_T as we do not want any of the
* auditing files to exceed 2GB. May be we will
* support this in future.
*/
/* error on write */
if (error != 0) {
return (error);
}
/* end of file system? */
if (left) {
/* update space counters */
/* which AR are done */
while (sz) {
0xffffffffU);
break;
b = cAR;
}
if (b != NULL)
au_dequeue(kctx, b);
return (ENOSPC);
} else { /* still space in file system */
/* if we've written an AR */
if (sp) {
/*
* free records up to last one copied.
*/
}
/* Update sizes */
/* reset auk_buffer pointers */
off = 0;
/* check exit conditions */
if ((fsblkcnt64_t)limit >
/*
* if we haven't put out a
* complete audit record,
* continue to process the
* audit queue until we reach
* the end of the record.
*/
partial = 1;
continue;
}
/*
* exit if complete record
*/
if (partial != 1)
return (ENOSPC);
}
}
/*
* force a complete audit
* record to the trail.
*/
if (partial == 0)
partial = 1;
/*
* Written data to AR boundry.
*/
if (partial != 1)
return (EFBIG);
}
}
}
} /* while(cMB) */
return (0);
}
/*
* Close an audit descriptor.
* Use the second parameter to indicate if it should be written or not.
*/
void
{
return;
*d = NULL;
/*
* syscall end, unless no syscall is active or the syscall is _exit.
*/
return;
}
}
/*
* via softcall. Otherwise, defer by queueing the record onto the tad; at
* syscall end time it will be pulled off.
*/
void
{
/* If not to be written, toss the record. */
return;
}
/* If no mem available, failing silently is the best recourse */
return;
}
/*
* All async events must be queued via softcall to avoid possible
* sleeping in high interrupt context. softcall will ensure it's
* done on a dedicated software-level interrupt thread.
*/
if (flag & AU_DONTBLOCK) {
audit_async_done(NULL, 0);
return;
}
/*
* If not an async event, defer by queuing onto the tad until
* syscall end. No locking is needed because the tad is per-thread.
*/
if (tad->tad_defer_head)
else
}
/*
* Save the time in the event header. If time is not specified (i.e., pointer
* is NULL), use the current time. This code is fairly ugly since it needs
* to support both 32- and 64-bit environments and can be called indirectly
* from both au_close() (for kernel audit) and from audit() (userland audit).
*/
/*ARGSUSED*/
static void
{
struct {
} tv;
gethrestime(&now);
}
#ifdef _LP64
if (size)
else
#endif
{
}
}
/*
* Close an audit descriptor.
* If time of event is specified, use it in the record, otherwise use the
* current time.
*/
void
{
int byte_count;
token_t *m; /* for potential sequence token */
/* If not to be written, toss the record */
return;
}
/* if auditing not enabled, then don't generate an audit record */
/*
* at system boot, neither is set yet we want to generate
* an audit record.
*/
if (e_type != AUE_SYSTEMBOOT) {
return;
}
}
/* Count up the bytes used in the record. */
/*
* add in size of header token (always present).
*/
byte_count += sizeof (char) + sizeof (int32_t) +
sizeof (char) + 2 * sizeof (short) + sizeof (timestruc_t);
if (kctx->auk_hostaddr_valid)
/*
* add in size of zonename token (zero if !AUDIT_ZONENAME)
*/
} else {
zone_length = 0;
}
/* add in size of (optional) trailer token */
byte_count += 7;
/* add in size of (optional) sequence token */
byte_count += 5;
/* build the header */
if (kctx->auk_hostaddr_valid)
else
/*
* If timestamp was specified, save it in header now. Otherwise,
*/
sizeof (char) + 2 * sizeof (short);
if (kctx->auk_hostaddr_valid)
}
/* append body of audit record */
/* add (optional) zonename token */
if (zone_length > 0) {
}
/* Add an (optional) sequence token. NULL offset if none */
/* get the sequence token */
m = au_to_seq();
/* link to audit record (i.e. don't pack the data) */
/*
* advance to count field of sequence token by skipping
* the token type byte.
*/
} else {
}
/* add (optional) trailer token */
AU_PACK);
}
/*
* 1 - use 64 bit version of audit tokens for 64 bit kernels.
* 0 - use 32 bit version of audit tokens for 32 bit kernels.
*/
#ifdef _LP64
#else
#endif
}
/*ARGSUSED*/
void
{
return;
audit_sync_block(kctx)) {
au_free_rec(m);
return;
}
/* Fill in date and time if needed */
}
/* address will be non-zero only if AUDIT_SEQ set */
kctx->auk_sequence++;
}
else
/* count # audit records put onto kernel audit queue */
}
/*
* Dequeue and free buffers upto and including "freeto"
* Keeps the queue lock long but acquires it only once when doing
* bulk dequeueing.
*/
static void
{
int n = 0;
do {
n++;
lastl = l;
l = l->next_rec;
/* Freeto must exist in the list */
while (m) {
l = m->next_rec;
au_free_rec(m);
m = l;
}
}
/*
* audit_sync_block()
* If we've reached the high water mark, we look at the policy to see
* if we sleep or we should drop the audit record.
* This function is called with the auk_queue.lock held and the check
* performed one time already as an optimization. Caller should unlock.
* Returns 1 if the caller needs to free the record.
*/
static int
{
/*
* Loop while we are at the high watermark.
*/
do {
/* just count # of dropped audit records */
return (1);
}
/* kick reader awake if its asleep */
/* keep count of # times blocked */
/* sleep now, until woken by reader */
return (0);
}
/*
* audit_async_block()
* if we've reached the high water mark, we look at the ahlt policy to see
* if we reboot we should drop the audit record.
* Returns 1 if blocked.
*/
static int
{
/* see if we've reached high water mark */
return (1);
}
return (0);
}
/*
* au_door_upcall. auditdoor() may change vp without notice, so
* some locking seems in order.
*
*/
#define AGAIN_TICKS 10
static int
{
int rc;
int retry = 1;
int ticks_to_wait;
while (retry == 1) {
/* non-zero means return results expected */
retry = 0;
else
return (rc);
&(kctx->auk_eagain_mutex),
lbolt + ticks_to_wait);
retry = 1;
} else
} /* end while (retry == 1) */
return (-1);
/* return code from door server */
}
/*
* Write an audit control message to the door handle. The message
* structure depends on message_code and at present the only control
* message defined is for a policy change. These are infrequent,
* so no memory is held for control messages.
*/
int
{
int rc;
switch (message_code) {
case AU_DBUF_POLICY:
break;
case AU_DBUF_SHUTDOWN:
break;
default:
return (1);
}
return (rc);
}
/*
* Write audit information to the door handle. au_doorio is called with
* one or more complete audit records on the queue and outputs those
* records in buffers of up to auk_queue.buflen in size.
*/
int
char *bp; /* start of free space in staging buffer */
unsigned char *cp; /* ptr to data to be moved */
int error; /* return from door upcall */
/*
* size (data left in au_membuf - space in buffer)
*/
/*
* partial_state is AU_DBUF_COMPLETE...LAST; see audit_door_infc.h
*/
int part = 0; /* partial audit record written */
int partial_state = AU_DBUF_COMPLETE;
/*
* Has the write buffer changed length due to a auditctl(2)?
* Initial allocation is from audit_start.c/audit_init()
*/
/* omit the 64 bit header */
}
goto nodata;
off = 0; /* no space used in buffer */
used = 0; /* no data processed in au_membuf */
/* start at beginning of buffer */
while (cMB) {
/* len to move */
/* move the data */
/* advance to next au_membuf */
used = 0;
}
/* advance to next audit record */
part = 0; /* have a complete record */
}
error = 0;
if (part)
else
if (error != 0)
goto nodata;
/*
* if we've successfully written an audit record,
* free records up to last full record copied
*/
if (sp)
/* Update size */
/* reset auk_dbuffer pointers */
off = 0;
}
} /* while(cMB) */
return (error);
}
/*
* Clean up thread audit state to clear out asynchronous audit record
* generation error recovery processing. Note that this is done on a
* per-thread basis and thus does not need any locking.
*/
void
{
/* clean up the tad unless called from softcall backend */
if (!(flags & AU_BACKEND)) {
}
/* clean out partial audit record */
}
}
/*
* implement the audit policy for asynchronous events generated within
* the kernel.
* XXX might need locks around audit_policy check.
*/
void
{
/* could not generate audit record, clean up */
kctx = GET_KCTX_GZ;
/* just drop the record and return */
if (((audit_policy & AUDIT_AHLT) == 0) ||
/* just count # of dropped audit records */
return;
}
/*
* There can be a lot of data in the audit queue. We
* will first sync the file systems then attempt to
* shutdown the kernel so that a memory dump is
* performed.
*/
sync();
sync();
/*
* now shut down. What a cruel world it has been
*/
panic("non-attributable halt. should dump core");
/* No return */
}
int
{
/* if audit state off, then no audit record generation */
return (1);
/*
* preselect asynchronous event
* XXX should we check for out-of-range???
*/
return (1);
return (0);
}
/*
* Complete auditing of an async event. The AU_DONTBLOCK flag to au_close will
* result in the backend routine being invoked from softcall, so all the real
* work can be done in a safe context.
*/
void
{
kctx = GET_KCTX_GZ;
}
/*
* Backend routine to complete an async audit. Invoked from softcall.
* (Note: the blocking and the queuing below both involve locking which can't
* be done safely in high interrupt context due to the chance of sleeping on
* the corresponding adaptive mutex. Hence the softcall.)
*/
static void
audit_async_finish_backend(void *addr)
{
return; /* won't happen unless softcall is broken */
kctx = GET_KCTX_GZ;
return;
}
/*
* Call au_close_time to complete the audit with the saved values.
*
* For the exit-prom event, use the current time instead of the
* saved time as a better approximation. (Because the time saved via
* gethrestime during prom-exit handling would not yet be caught up
* after the system was idled in the debugger for a period of time.)
*/
} else {
}
}