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