bsm_audit.c revision 155131
1/*
2 * Copyright (c) 2004 Apple Computer, Inc.
3 * Copyright (c) 2005 SPARTA, Inc.
4 * All rights reserved.
5 *
6 * This code was developed in part by Robert N. M. Watson, Senior Principal
7 * Scientist, SPARTA, Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1.  Redistributions of source code must retain the above copyright
13 *     notice, this list of conditions and the following disclaimer.
14 * 2.  Redistributions in binary form must reproduce the above copyright
15 *     notice, this list of conditions and the following disclaimer in the
16 *     documentation and/or other materials provided with the distribution.
17 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
18 *     its contributors may be used to endorse or promote products derived
19 *     from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
25 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
30 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 *
33 * $P4: //depot/projects/trustedbsd/openbsm/libbsm/bsm_audit.c#18 $
34 */
35
36#include <sys/types.h>
37#include <sys/queue.h>
38
39#include <bsm/audit_internal.h>
40#include <bsm/libbsm.h>
41
42#include <errno.h>
43#include <pthread.h>
44#include <stdlib.h>
45#include <string.h>
46
47/* array of used descriptors */
48static au_record_t	*open_desc_table[MAX_AUDIT_RECORDS];
49
50/* The current number of active record descriptors */
51static int	bsm_rec_count = 0;
52
53/*
54 * Records that can be recycled are maintained in the list given below.  The
55 * maximum number of elements that can be present in this list is bounded by
56 * MAX_AUDIT_RECORDS.  Memory allocated for these records are never freed.
57 */
58static LIST_HEAD(, au_record)	bsm_free_q;
59
60static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
61
62/*
63 * This call frees a token_t and its internal data.
64 */
65void
66au_free_token(token_t *tok)
67{
68
69	if (tok != NULL) {
70		if (tok->t_data)
71			free(tok->t_data);
72		free(tok);
73	}
74}
75
76/*
77 * This call reserves memory for the audit record.  Memory must be guaranteed
78 * before any auditable event can be generated.  The au_record_t structure
79 * maintains a reference to the memory allocated above and also the list of
80 * tokens associated with this record.  Descriptors are recyled once the
81 * records are added to the audit trail following au_close().
82 */
83int
84au_open(void)
85{
86	au_record_t *rec = NULL;
87
88	pthread_mutex_lock(&mutex);
89
90	if (bsm_rec_count == 0)
91		LIST_INIT(&bsm_free_q);
92
93	/*
94	 * Find an unused descriptor, remove it from the free list, mark as
95	 * used.
96	 */
97	if (!LIST_EMPTY(&bsm_free_q)) {
98		rec = LIST_FIRST(&bsm_free_q);
99		rec->used = 1;
100		LIST_REMOVE(rec, au_rec_q);
101	}
102
103	pthread_mutex_unlock(&mutex);
104
105	if (rec == NULL) {
106		/*
107		 * Create a new au_record_t if no descriptors are available.
108		 */
109		rec = malloc (sizeof(au_record_t));
110		if (rec == NULL)
111			return (-1);
112
113		rec->data = malloc (MAX_AUDIT_RECORD_SIZE * sizeof(u_char));
114		if (rec->data == NULL) {
115			free(rec);
116			errno = ENOMEM;
117			return (-1);
118		}
119
120		pthread_mutex_lock(&mutex);
121
122		if (bsm_rec_count == MAX_AUDIT_RECORDS) {
123			pthread_mutex_unlock(&mutex);
124			free(rec->data);
125			free(rec);
126
127			/* XXX We need to increase size of MAX_AUDIT_RECORDS */
128			errno = ENOMEM;
129			return (-1);
130		}
131		rec->desc = bsm_rec_count;
132		open_desc_table[bsm_rec_count] = rec;
133		bsm_rec_count++;
134
135		pthread_mutex_unlock(&mutex);
136
137	}
138
139	memset(rec->data, 0, MAX_AUDIT_RECORD_SIZE);
140
141	TAILQ_INIT(&rec->token_q);
142	rec->len = 0;
143	rec->used = 1;
144
145	return (rec->desc);
146}
147
148/*
149 * Store the token with the record descriptor.
150 *
151 * Don't permit writing more to the buffer than would let the trailer be
152 * appended later.
153 */
154int
155au_write(int d, token_t *tok)
156{
157	au_record_t *rec;
158
159	if (tok == NULL) {
160		errno = EINVAL;
161		return (-1); /* Invalid Token */
162	}
163
164	/* Write the token to the record descriptor */
165	rec = open_desc_table[d];
166	if ((rec == NULL) || (rec->used == 0)) {
167		errno = EINVAL;
168		return (-1); /* Invalid descriptor */
169	}
170
171	if (rec->len + tok->len + BSM_TRAILER_SIZE > MAX_AUDIT_RECORD_SIZE) {
172		errno = ENOMEM;
173		return (-1);
174	}
175
176	/* Add the token to the tail */
177	/*
178	 * XXX Not locking here -- we should not be writing to
179	 * XXX the same descriptor from different threads
180	 */
181	TAILQ_INSERT_TAIL(&rec->token_q, tok, tokens);
182
183	rec->len += tok->len; /* grow record length by token size bytes */
184
185	/* Token should not be available after this call */
186	tok = NULL;
187	return (0); /* Success */
188}
189
190/*
191 * Assemble an audit record out of its tokens, including allocating header and
192 * trailer tokens.  Does not free the token chain, which must be done by the
193 * caller if desirable.
194 *
195 * XXX: Assumes there is sufficient space for the header and trailer.
196 */
197static int
198au_assemble(au_record_t *rec, short event)
199{
200	token_t *header, *tok, *trailer;
201	size_t tot_rec_size;
202	u_char *dptr;
203	int error;
204
205	tot_rec_size = rec->len + BSM_HEADER_SIZE + BSM_TRAILER_SIZE;
206	header = au_to_header32(tot_rec_size, event, 0);
207	if (header == NULL)
208		return (-1);
209
210	trailer = au_to_trailer(tot_rec_size);
211	if (trailer == NULL) {
212		error = errno;
213		au_free_token(header);
214		errno = error;
215		return (-1);
216	}
217
218	TAILQ_INSERT_HEAD(&rec->token_q, header, tokens);
219	TAILQ_INSERT_TAIL(&rec->token_q, trailer, tokens);
220
221	rec->len = tot_rec_size;
222	dptr = rec->data;
223
224	TAILQ_FOREACH(tok, &rec->token_q, tokens) {
225		memcpy(dptr, tok->t_data, tok->len);
226		dptr += tok->len;
227	}
228
229	return (0);
230}
231
232/*
233 * Given a record that is no longer of interest, tear it down and convert to a
234 * free record.
235 */
236static void
237au_teardown(au_record_t *rec)
238{
239	token_t *tok;
240
241	/* Free the token list */
242	while ((tok = TAILQ_FIRST(&rec->token_q)) != NULL) {
243		TAILQ_REMOVE(&rec->token_q, tok, tokens);
244		free(tok->t_data);
245		free(tok);
246	}
247
248	rec->used = 0;
249	rec->len = 0;
250
251	pthread_mutex_lock(&mutex);
252
253	/* Add the record to the freelist tail */
254	LIST_INSERT_HEAD(&bsm_free_q, rec, au_rec_q);
255
256	pthread_mutex_unlock(&mutex);
257}
258
259/*
260 * Add the header token, identify any missing tokens.  Write out the tokens to
261 * the record memory and finally, call audit.
262 */
263int au_close(int d, int keep, short event)
264{
265	au_record_t *rec;
266	size_t tot_rec_size;
267	int retval = 0;
268
269	rec = open_desc_table[d];
270	if ((rec == NULL) || (rec->used == 0)) {
271		errno = EINVAL;
272		return (-1); /* Invalid descriptor */
273	}
274
275	if (!keep) {
276		retval = 0;
277		goto cleanup;
278	}
279
280
281	tot_rec_size = rec->len + BSM_HEADER_SIZE + BSM_TRAILER_SIZE;
282
283	if (tot_rec_size > MAX_AUDIT_RECORD_SIZE) {
284		/*
285		 * XXXRW: Since au_write() is supposed to prevent this, spew
286		 * an error here.
287		 */
288		fprintf(stderr, "au_close failed");
289		errno = ENOMEM;
290		retval = -1;
291		goto cleanup;
292	}
293
294	if (au_assemble(rec, event) < 0) {
295		/*
296		 * XXXRW: This is also not supposed to happen, but might if we
297		 * are unable to allocate header and trailer memory.
298		 */
299		retval = -1;
300		goto cleanup;
301	}
302
303	/* Call the kernel interface to audit */
304	retval = audit(rec->data, rec->len);
305
306cleanup:
307	/* CLEANUP */
308	au_teardown(rec);
309	return (retval);
310}
311
312/*
313 * au_close(), except onto an in-memory buffer.  Buffer size as an argument,
314 * record size returned via same argument on success.
315 */
316int
317au_close_buffer(int d, short event, u_char *buffer, size_t *buflen)
318{
319	size_t tot_rec_size;
320	au_record_t *rec;
321	int retval;
322
323	rec = open_desc_table[d];
324	if ((rec == NULL) || (rec->used == 0)) {
325		errno = EINVAL;
326		return (-1);
327	}
328
329	retval = 0;
330	tot_rec_size = rec->len + BSM_HEADER_SIZE + BSM_TRAILER_SIZE;
331	if ((tot_rec_size > MAX_AUDIT_RECORD_SIZE) ||
332	    (tot_rec_size > *buflen)) {
333		/*
334		 * XXXRW: See au_close() comment.
335		 */
336		fprintf(stderr, "au_close_buffer failed %zd", tot_rec_size);
337		errno = ENOMEM;
338		retval = -1;
339		goto cleanup;
340	}
341
342	if (au_assemble(rec, event) < 0) {
343		/* XXXRW: See au_close() comment. */
344		retval = -1;
345		goto cleanup;
346	}
347
348	memcpy(buffer, rec->data, rec->len);
349	*buflen = rec->len;
350
351cleanup:
352	au_teardown(rec);
353	return (retval);
354}
355