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