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