c74c7b73a0
contrib/openbsm (svn merge) and src/sys/{bsm,security/audit} (manual merge). Hook up bsm_domain.c and bsm_socket_type.c to the libbsm build along with man pages, add audit_bsm_domain.c and audit_bsm_socket_type.c to the kernel environment. OpenBSM history for imported revisions below for reference. MFC after: 1 month Sponsored by: Apple Inc. Obtained from: TrustedBSD Project OpenBSM 1.1 alpha 5 - Stub libauditd(3) man page added. - All BSM error number constants with BSM_ERRNO_. - Interfaces to convert between local and BSM socket types and protocol families have been added: au_bsm_to_domain(3), au_bsm_to_socket_type(3), au_domain_to_bsm(3), and au_socket_type_to_bsm(3), along with definitions of constants in audit_domain.h and audit_socket_type.h. This improves interoperability by converting local constant spaces, which vary by OS, to and from Solaris constants (where available) or OpenBSM constants for protocol domains not present in Solaris (a fair number). These routines should be used when generating and interpreting extended socket tokens. - Fix build warnings with full gcc warnings enabled on most supported platforms. - Don't compile error strings into bsm_errno.c when building it in the kernel environment. - When started by launchd, use the label com.apple.auditd rather than org.trustedbsd.auditd.
452 lines
11 KiB
C
452 lines
11 KiB
C
/*-
|
|
* Copyright (c) 2004 Apple Inc.
|
|
* Copyright (c) 2005 SPARTA, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code was developed in part by Robert N. M. Watson, Senior Principal
|
|
* Scientist, SPARTA, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
|
* its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* $P4: //depot/projects/trustedbsd/openbsm/libbsm/bsm_audit.c#35 $
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <config/config.h>
|
|
#ifdef HAVE_FULL_QUEUE_H
|
|
#include <sys/queue.h>
|
|
#else
|
|
#include <compat/queue.h>
|
|
#endif
|
|
|
|
#include <bsm/audit_internal.h>
|
|
#include <bsm/libbsm.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <errno.h>
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
#include <pthread.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* array of used descriptors */
|
|
static au_record_t *open_desc_table[MAX_AUDIT_RECORDS];
|
|
|
|
/* The current number of active record descriptors */
|
|
static int audit_rec_count = 0;
|
|
|
|
/*
|
|
* Records that can be recycled are maintained in the list given below. The
|
|
* maximum number of elements that can be present in this list is bounded by
|
|
* MAX_AUDIT_RECORDS. Memory allocated for these records are never freed.
|
|
*/
|
|
static LIST_HEAD(, au_record) audit_free_q;
|
|
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
#endif
|
|
|
|
/*
|
|
* This call frees a token_t and its internal data.
|
|
*/
|
|
void
|
|
au_free_token(token_t *tok)
|
|
{
|
|
|
|
if (tok != NULL) {
|
|
if (tok->t_data)
|
|
free(tok->t_data);
|
|
free(tok);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This call reserves memory for the audit record. Memory must be guaranteed
|
|
* before any auditable event can be generated. The au_record_t structure
|
|
* maintains a reference to the memory allocated above and also the list of
|
|
* tokens associated with this record. Descriptors are recyled once the
|
|
* records are added to the audit trail following au_close().
|
|
*/
|
|
int
|
|
au_open(void)
|
|
{
|
|
au_record_t *rec = NULL;
|
|
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
pthread_mutex_lock(&mutex);
|
|
#endif
|
|
|
|
if (audit_rec_count == 0)
|
|
LIST_INIT(&audit_free_q);
|
|
|
|
/*
|
|
* Find an unused descriptor, remove it from the free list, mark as
|
|
* used.
|
|
*/
|
|
if (!LIST_EMPTY(&audit_free_q)) {
|
|
rec = LIST_FIRST(&audit_free_q);
|
|
rec->used = 1;
|
|
LIST_REMOVE(rec, au_rec_q);
|
|
}
|
|
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
pthread_mutex_unlock(&mutex);
|
|
#endif
|
|
|
|
if (rec == NULL) {
|
|
/*
|
|
* Create a new au_record_t if no descriptors are available.
|
|
*/
|
|
rec = malloc (sizeof(au_record_t));
|
|
if (rec == NULL)
|
|
return (-1);
|
|
|
|
rec->data = malloc (MAX_AUDIT_RECORD_SIZE * sizeof(u_char));
|
|
if (rec->data == NULL) {
|
|
free(rec);
|
|
errno = ENOMEM;
|
|
return (-1);
|
|
}
|
|
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
pthread_mutex_lock(&mutex);
|
|
#endif
|
|
|
|
if (audit_rec_count == MAX_AUDIT_RECORDS) {
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
pthread_mutex_unlock(&mutex);
|
|
#endif
|
|
free(rec->data);
|
|
free(rec);
|
|
|
|
/* XXX We need to increase size of MAX_AUDIT_RECORDS */
|
|
errno = ENOMEM;
|
|
return (-1);
|
|
}
|
|
rec->desc = audit_rec_count;
|
|
open_desc_table[audit_rec_count] = rec;
|
|
audit_rec_count++;
|
|
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
pthread_mutex_unlock(&mutex);
|
|
#endif
|
|
|
|
}
|
|
|
|
memset(rec->data, 0, MAX_AUDIT_RECORD_SIZE);
|
|
|
|
TAILQ_INIT(&rec->token_q);
|
|
rec->len = 0;
|
|
rec->used = 1;
|
|
|
|
return (rec->desc);
|
|
}
|
|
|
|
/*
|
|
* Store the token with the record descriptor.
|
|
*
|
|
* Don't permit writing more to the buffer than would let the trailer be
|
|
* appended later.
|
|
*/
|
|
int
|
|
au_write(int d, token_t *tok)
|
|
{
|
|
au_record_t *rec;
|
|
|
|
if (tok == NULL) {
|
|
errno = EINVAL;
|
|
return (-1); /* Invalid Token */
|
|
}
|
|
|
|
/* Write the token to the record descriptor */
|
|
rec = open_desc_table[d];
|
|
if ((rec == NULL) || (rec->used == 0)) {
|
|
errno = EINVAL;
|
|
return (-1); /* Invalid descriptor */
|
|
}
|
|
|
|
if (rec->len + tok->len + AUDIT_TRAILER_SIZE > MAX_AUDIT_RECORD_SIZE) {
|
|
errno = ENOMEM;
|
|
return (-1);
|
|
}
|
|
|
|
/* Add the token to the tail */
|
|
/*
|
|
* XXX Not locking here -- we should not be writing to
|
|
* XXX the same descriptor from different threads
|
|
*/
|
|
TAILQ_INSERT_TAIL(&rec->token_q, tok, tokens);
|
|
|
|
rec->len += tok->len; /* grow record length by token size bytes */
|
|
|
|
/* Token should not be available after this call */
|
|
tok = NULL;
|
|
return (0); /* Success */
|
|
}
|
|
|
|
/*
|
|
* Assemble an audit record out of its tokens, including allocating header and
|
|
* trailer tokens. Does not free the token chain, which must be done by the
|
|
* caller if desirable.
|
|
*
|
|
* XXX: Assumes there is sufficient space for the header and trailer.
|
|
*/
|
|
static int
|
|
au_assemble(au_record_t *rec, short event)
|
|
{
|
|
#ifdef HAVE_AUDIT_SYSCALLS
|
|
struct in6_addr *aptr;
|
|
struct auditinfo_addr aia;
|
|
struct timeval tm;
|
|
size_t hdrsize;
|
|
#endif /* HAVE_AUDIT_SYSCALLS */
|
|
token_t *header, *tok, *trailer;
|
|
size_t tot_rec_size;
|
|
u_char *dptr;
|
|
int error;
|
|
|
|
#ifdef HAVE_AUDIT_SYSCALLS
|
|
/*
|
|
* Grab the size of the address family stored in the kernel's audit
|
|
* state.
|
|
*/
|
|
aia.ai_termid.at_type = AU_IPv4;
|
|
aia.ai_termid.at_addr[0] = INADDR_ANY;
|
|
if (auditon(A_GETKAUDIT, &aia, sizeof(aia)) < 0) {
|
|
if (errno != ENOSYS && errno != EPERM)
|
|
return (-1);
|
|
#endif /* HAVE_AUDIT_SYSCALLS */
|
|
tot_rec_size = rec->len + AUDIT_HEADER_SIZE +
|
|
AUDIT_TRAILER_SIZE;
|
|
header = au_to_header(tot_rec_size, event, 0);
|
|
#ifdef HAVE_AUDIT_SYSCALLS
|
|
} else {
|
|
if (gettimeofday(&tm, NULL) < 0)
|
|
return (-1);
|
|
switch (aia.ai_termid.at_type) {
|
|
case AU_IPv4:
|
|
hdrsize = (aia.ai_termid.at_addr[0] == INADDR_ANY) ?
|
|
AUDIT_HEADER_SIZE : AUDIT_HEADER_EX_SIZE(&aia);
|
|
break;
|
|
case AU_IPv6:
|
|
aptr = (struct in6_addr *)&aia.ai_termid.at_addr[0];
|
|
hdrsize =
|
|
(IN6_IS_ADDR_UNSPECIFIED(aptr)) ?
|
|
AUDIT_HEADER_SIZE : AUDIT_HEADER_EX_SIZE(&aia);
|
|
break;
|
|
default:
|
|
return (-1);
|
|
}
|
|
tot_rec_size = rec->len + hdrsize + AUDIT_TRAILER_SIZE;
|
|
/*
|
|
* A header size greater then AUDIT_HEADER_SIZE means
|
|
* that we are using an extended header.
|
|
*/
|
|
if (hdrsize > AUDIT_HEADER_SIZE)
|
|
header = au_to_header32_ex_tm(tot_rec_size, event,
|
|
0, tm, &aia);
|
|
else
|
|
header = au_to_header(tot_rec_size, event, 0);
|
|
}
|
|
#endif /* HAVE_AUDIT_SYSCALLS */
|
|
if (header == NULL)
|
|
return (-1);
|
|
|
|
trailer = au_to_trailer(tot_rec_size);
|
|
if (trailer == NULL) {
|
|
error = errno;
|
|
au_free_token(header);
|
|
errno = error;
|
|
return (-1);
|
|
}
|
|
|
|
TAILQ_INSERT_HEAD(&rec->token_q, header, tokens);
|
|
TAILQ_INSERT_TAIL(&rec->token_q, trailer, tokens);
|
|
|
|
rec->len = tot_rec_size;
|
|
dptr = rec->data;
|
|
|
|
TAILQ_FOREACH(tok, &rec->token_q, tokens) {
|
|
memcpy(dptr, tok->t_data, tok->len);
|
|
dptr += tok->len;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Given a record that is no longer of interest, tear it down and convert to a
|
|
* free record.
|
|
*/
|
|
static void
|
|
au_teardown(au_record_t *rec)
|
|
{
|
|
token_t *tok;
|
|
|
|
/* Free the token list */
|
|
while ((tok = TAILQ_FIRST(&rec->token_q)) != NULL) {
|
|
TAILQ_REMOVE(&rec->token_q, tok, tokens);
|
|
free(tok->t_data);
|
|
free(tok);
|
|
}
|
|
|
|
rec->used = 0;
|
|
rec->len = 0;
|
|
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
pthread_mutex_lock(&mutex);
|
|
#endif
|
|
|
|
/* Add the record to the freelist tail */
|
|
LIST_INSERT_HEAD(&audit_free_q, rec, au_rec_q);
|
|
|
|
#ifdef HAVE_PTHREAD_MUTEX_LOCK
|
|
pthread_mutex_unlock(&mutex);
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_AUDIT_SYSCALLS
|
|
/*
|
|
* Add the header token, identify any missing tokens. Write out the tokens to
|
|
* the record memory and finally, call audit.
|
|
*/
|
|
int
|
|
au_close(int d, int keep, short event)
|
|
{
|
|
au_record_t *rec;
|
|
size_t tot_rec_size;
|
|
int retval = 0;
|
|
|
|
rec = open_desc_table[d];
|
|
if ((rec == NULL) || (rec->used == 0)) {
|
|
errno = EINVAL;
|
|
return (-1); /* Invalid descriptor */
|
|
}
|
|
|
|
if (keep == AU_TO_NO_WRITE) {
|
|
retval = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
tot_rec_size = rec->len + MAX_AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE;
|
|
|
|
if (tot_rec_size > MAX_AUDIT_RECORD_SIZE) {
|
|
/*
|
|
* XXXRW: Since au_write() is supposed to prevent this, spew
|
|
* an error here.
|
|
*/
|
|
fprintf(stderr, "au_close failed");
|
|
errno = ENOMEM;
|
|
retval = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (au_assemble(rec, event) < 0) {
|
|
/*
|
|
* XXXRW: This is also not supposed to happen, but might if we
|
|
* are unable to allocate header and trailer memory.
|
|
*/
|
|
retval = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Call the kernel interface to audit */
|
|
retval = audit(rec->data, rec->len);
|
|
|
|
cleanup:
|
|
/* CLEANUP */
|
|
au_teardown(rec);
|
|
return (retval);
|
|
}
|
|
#endif /* HAVE_AUDIT_SYSCALLS */
|
|
|
|
/*
|
|
* au_close(), except onto an in-memory buffer. Buffer size as an argument,
|
|
* record size returned via same argument on success.
|
|
*/
|
|
int
|
|
au_close_buffer(int d, short event, u_char *buffer, size_t *buflen)
|
|
{
|
|
size_t tot_rec_size;
|
|
au_record_t *rec;
|
|
int retval;
|
|
|
|
rec = open_desc_table[d];
|
|
if ((rec == NULL) || (rec->used == 0)) {
|
|
errno = EINVAL;
|
|
return (-1);
|
|
}
|
|
|
|
retval = 0;
|
|
tot_rec_size = rec->len + MAX_AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE;
|
|
if ((tot_rec_size > MAX_AUDIT_RECORD_SIZE) ||
|
|
(tot_rec_size > *buflen)) {
|
|
/*
|
|
* XXXRW: See au_close() comment.
|
|
*/
|
|
fprintf(stderr, "au_close_buffer failed %zd", tot_rec_size);
|
|
errno = ENOMEM;
|
|
retval = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (au_assemble(rec, event) < 0) {
|
|
/* XXXRW: See au_close() comment. */
|
|
retval = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
memcpy(buffer, rec->data, rec->len);
|
|
*buflen = rec->len;
|
|
|
|
cleanup:
|
|
au_teardown(rec);
|
|
return (retval);
|
|
}
|
|
|
|
/*
|
|
* au_close_token() returns the byte format of a token_t. This won't
|
|
* generally be used by applications, but is quite useful for writing test
|
|
* tools. Will free the token on either success or failure.
|
|
*/
|
|
int
|
|
au_close_token(token_t *tok, u_char *buffer, size_t *buflen)
|
|
{
|
|
|
|
if (tok->len > *buflen) {
|
|
au_free_token(tok);
|
|
errno = ENOMEM;
|
|
return (EINVAL);
|
|
}
|
|
|
|
memcpy(buffer, tok->t_data, tok->len);
|
|
*buflen = tok->len;
|
|
au_free_token(tok);
|
|
return (0);
|
|
}
|