xref: /freebsd/contrib/openbsm/libbsm/bsm_audit.c (revision 7ef62cebc2f965b0f640263e179276928885e33d)
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 */
56 static au_record_t	*open_desc_table[MAX_AUDIT_RECORDS];
57 
58 /* The current number of active record descriptors */
59 static 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  */
66 static LIST_HEAD(, au_record)	audit_free_q;
67 
68 #ifdef HAVE_PTHREAD_MUTEX_LOCK
69 static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
70 #endif
71 
72 /*
73  * This call frees a token_t and its internal data.
74  */
75 void
76 au_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  */
93 int
94 au_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  */
174 int
175 au_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  */
217 static int
218 au_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  */
304 static void
305 au_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  */
336 int
337 au_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 
379 cleanup:
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  */
390 int
391 au_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 
425 cleanup:
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  */
435 int
436 au_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