xref: /freebsd/contrib/openbsm/libbsm/bsm_audit.c (revision f4b37ed0f8b307b1f3f0f630ca725d68f1dff30d)
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 */
58 static au_record_t	*open_desc_table[MAX_AUDIT_RECORDS];
59 
60 /* The current number of active record descriptors */
61 static 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  */
68 static LIST_HEAD(, au_record)	audit_free_q;
69 
70 #ifdef HAVE_PTHREAD_MUTEX_LOCK
71 static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
72 #endif
73 
74 /*
75  * This call frees a token_t and its internal data.
76  */
77 void
78 au_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  */
95 int
96 au_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  */
176 int
177 au_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  */
219 static int
220 au_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  */
306 static void
307 au_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  */
338 int
339 au_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 
381 cleanup:
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  */
392 int
393 au_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 
427 cleanup:
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  */
437 int
438 au_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