xref: /freebsd/contrib/openbsm/libbsm/bsm_audit.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
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 */
54 static au_record_t	*open_desc_table[MAX_AUDIT_RECORDS];
55 
56 /* The current number of active record descriptors */
57 static 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  */
64 static LIST_HEAD(, au_record)	audit_free_q;
65 
66 static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
67 
68 /*
69  * This call frees a token_t and its internal data.
70  */
71 void
72 au_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  */
89 int
90 au_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  */
160 int
161 au_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  */
203 static int
204 au_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  */
242 static void
243 au_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  */
270 int
271 au_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 
313 cleanup:
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  */
324 int
325 au_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 
359 cleanup:
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  */
369 int
370 au_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