bsm_audit.c revision 161631
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#28 $
34 */
35
36#include <sys/types.h>
37
38#include <config/config.h>
39#ifdef HAVE_FULL_QUEUE_H
40#include <sys/queue.h>
41#else
42#include <compat/queue.h>
43#endif
44
45#include <bsm/audit_internal.h>
46#include <bsm/libbsm.h>
47
48#include <errno.h>
49#include <pthread.h>
50#include <stdlib.h>
51#include <string.h>
52
53/* array of used descriptors */
54static au_record_t	*open_desc_table[MAX_AUDIT_RECORDS];
55
56/* The current number of active record descriptors */
57static int	audit_rec_count = 0;
58
59/*
60 * Records that can be recycled are maintained in the list given below.  The
61 * maximum number of elements that can be present in this list is bounded by
62 * MAX_AUDIT_RECORDS.  Memory allocated for these records are never freed.
63 */
64static LIST_HEAD(, au_record)	audit_free_q;
65
66static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
67
68/*
69 * This call frees a token_t and its internal data.
70 */
71void
72au_free_token(token_t *tok)
73{
74
75	if (tok != NULL) {
76		if (tok->t_data)
77			free(tok->t_data);
78		free(tok);
79	}
80}
81
82/*
83 * This call reserves memory for the audit record.  Memory must be guaranteed
84 * before any auditable event can be generated.  The au_record_t structure
85 * maintains a reference to the memory allocated above and also the list of
86 * tokens associated with this record.  Descriptors are recyled once the
87 * records are added to the audit trail following au_close().
88 */
89int
90au_open(void)
91{
92	au_record_t *rec = NULL;
93
94	pthread_mutex_lock(&mutex);
95
96	if (audit_rec_count == 0)
97		LIST_INIT(&audit_free_q);
98
99	/*
100	 * Find an unused descriptor, remove it from the free list, mark as
101	 * used.
102	 */
103	if (!LIST_EMPTY(&audit_free_q)) {
104		rec = LIST_FIRST(&audit_free_q);
105		rec->used = 1;
106		LIST_REMOVE(rec, au_rec_q);
107	}
108
109	pthread_mutex_unlock(&mutex);
110
111	if (rec == NULL) {
112		/*
113		 * Create a new au_record_t if no descriptors are available.
114		 */
115		rec = malloc (sizeof(au_record_t));
116		if (rec == NULL)
117			return (-1);
118
119		rec->data = malloc (MAX_AUDIT_RECORD_SIZE * sizeof(u_char));
120		if (rec->data == NULL) {
121			free(rec);
122			errno = ENOMEM;
123			return (-1);
124		}
125
126		pthread_mutex_lock(&mutex);
127
128		if (audit_rec_count == MAX_AUDIT_RECORDS) {
129			pthread_mutex_unlock(&mutex);
130			free(rec->data);
131			free(rec);
132
133			/* XXX We need to increase size of MAX_AUDIT_RECORDS */
134			errno = ENOMEM;
135			return (-1);
136		}
137		rec->desc = audit_rec_count;
138		open_desc_table[audit_rec_count] = rec;
139		audit_rec_count++;
140
141		pthread_mutex_unlock(&mutex);
142
143	}
144
145	memset(rec->data, 0, MAX_AUDIT_RECORD_SIZE);
146
147	TAILQ_INIT(&rec->token_q);
148	rec->len = 0;
149	rec->used = 1;
150
151	return (rec->desc);
152}
153
154/*
155 * Store the token with the record descriptor.
156 *
157 * Don't permit writing more to the buffer than would let the trailer be
158 * appended later.
159 */
160int
161au_write(int d, token_t *tok)
162{
163	au_record_t *rec;
164
165	if (tok == NULL) {
166		errno = EINVAL;
167		return (-1); /* Invalid Token */
168	}
169
170	/* Write the token to the record descriptor */
171	rec = open_desc_table[d];
172	if ((rec == NULL) || (rec->used == 0)) {
173		errno = EINVAL;
174		return (-1); /* Invalid descriptor */
175	}
176
177	if (rec->len + tok->len + AUDIT_TRAILER_SIZE > MAX_AUDIT_RECORD_SIZE) {
178		errno = ENOMEM;
179		return (-1);
180	}
181
182	/* Add the token to the tail */
183	/*
184	 * XXX Not locking here -- we should not be writing to
185	 * XXX the same descriptor from different threads
186	 */
187	TAILQ_INSERT_TAIL(&rec->token_q, tok, tokens);
188
189	rec->len += tok->len; /* grow record length by token size bytes */
190
191	/* Token should not be available after this call */
192	tok = NULL;
193	return (0); /* Success */
194}
195
196/*
197 * Assemble an audit record out of its tokens, including allocating header and
198 * trailer tokens.  Does not free the token chain, which must be done by the
199 * caller if desirable.
200 *
201 * XXX: Assumes there is sufficient space for the header and trailer.
202 */
203static int
204au_assemble(au_record_t *rec, short event)
205{
206	token_t *header, *tok, *trailer;
207	size_t tot_rec_size;
208	u_char *dptr;
209	int error;
210
211	tot_rec_size = rec->len + AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE;
212	header = au_to_header32(tot_rec_size, event, 0);
213	if (header == NULL)
214		return (-1);
215
216	trailer = au_to_trailer(tot_rec_size);
217	if (trailer == NULL) {
218		error = errno;
219		au_free_token(header);
220		errno = error;
221		return (-1);
222	}
223
224	TAILQ_INSERT_HEAD(&rec->token_q, header, tokens);
225	TAILQ_INSERT_TAIL(&rec->token_q, trailer, tokens);
226
227	rec->len = tot_rec_size;
228	dptr = rec->data;
229
230	TAILQ_FOREACH(tok, &rec->token_q, tokens) {
231		memcpy(dptr, tok->t_data, tok->len);
232		dptr += tok->len;
233	}
234
235	return (0);
236}
237
238/*
239 * Given a record that is no longer of interest, tear it down and convert to a
240 * free record.
241 */
242static void
243au_teardown(au_record_t *rec)
244{
245	token_t *tok;
246
247	/* Free the token list */
248	while ((tok = TAILQ_FIRST(&rec->token_q)) != NULL) {
249		TAILQ_REMOVE(&rec->token_q, tok, tokens);
250		free(tok->t_data);
251		free(tok);
252	}
253
254	rec->used = 0;
255	rec->len = 0;
256
257	pthread_mutex_lock(&mutex);
258
259	/* Add the record to the freelist tail */
260	LIST_INSERT_HEAD(&audit_free_q, rec, au_rec_q);
261
262	pthread_mutex_unlock(&mutex);
263}
264
265#ifdef HAVE_AUDIT_SYSCALLS
266/*
267 * Add the header token, identify any missing tokens.  Write out the tokens to
268 * the record memory and finally, call audit.
269 */
270int
271au_close(int d, int keep, short event)
272{
273	au_record_t *rec;
274	size_t tot_rec_size;
275	int retval = 0;
276
277	rec = open_desc_table[d];
278	if ((rec == NULL) || (rec->used == 0)) {
279		errno = EINVAL;
280		return (-1); /* Invalid descriptor */
281	}
282
283	if (keep == AU_TO_NO_WRITE) {
284		retval = 0;
285		goto cleanup;
286	}
287
288	tot_rec_size = rec->len + AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE;
289
290	if (tot_rec_size > MAX_AUDIT_RECORD_SIZE) {
291		/*
292		 * XXXRW: Since au_write() is supposed to prevent this, spew
293		 * an error here.
294		 */
295		fprintf(stderr, "au_close failed");
296		errno = ENOMEM;
297		retval = -1;
298		goto cleanup;
299	}
300
301	if (au_assemble(rec, event) < 0) {
302		/*
303		 * XXXRW: This is also not supposed to happen, but might if we
304		 * are unable to allocate header and trailer memory.
305		 */
306		retval = -1;
307		goto cleanup;
308	}
309
310	/* Call the kernel interface to audit */
311	retval = audit(rec->data, rec->len);
312
313cleanup:
314	/* CLEANUP */
315	au_teardown(rec);
316	return (retval);
317}
318#endif /* HAVE_AUDIT_SYSCALLS */
319
320/*
321 * au_close(), except onto an in-memory buffer.  Buffer size as an argument,
322 * record size returned via same argument on success.
323 */
324int
325au_close_buffer(int d, short event, u_char *buffer, size_t *buflen)
326{
327	size_t tot_rec_size;
328	au_record_t *rec;
329	int retval;
330
331	rec = open_desc_table[d];
332	if ((rec == NULL) || (rec->used == 0)) {
333		errno = EINVAL;
334		return (-1);
335	}
336
337	retval = 0;
338	tot_rec_size = rec->len + AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE;
339	if ((tot_rec_size > MAX_AUDIT_RECORD_SIZE) ||
340	    (tot_rec_size > *buflen)) {
341		/*
342		 * XXXRW: See au_close() comment.
343		 */
344		fprintf(stderr, "au_close_buffer failed %zd", tot_rec_size);
345		errno = ENOMEM;
346		retval = -1;
347		goto cleanup;
348	}
349
350	if (au_assemble(rec, event) < 0) {
351		/* XXXRW: See au_close() comment. */
352		retval = -1;
353		goto cleanup;
354	}
355
356	memcpy(buffer, rec->data, rec->len);
357	*buflen = rec->len;
358
359cleanup:
360	au_teardown(rec);
361	return (retval);
362}
363
364/*
365 * au_close_token() returns the byte format of a token_t.  This won't
366 * generally be used by applications, but is quite useful for writing test
367 * tools.  Will free the token on either success or failure.
368 */
369int
370au_close_token(token_t *tok, u_char *buffer, size_t *buflen)
371{
372
373	if (tok->len > *buflen) {
374		au_free_token(tok);
375		errno = ENOMEM;
376		return (EINVAL);
377	}
378
379	memcpy(buffer, tok->t_data, tok->len);
380	*buflen = tok->len;
381	au_free_token(tok);
382	return (0);
383}
384