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
au_free_token(token_t * tok)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
au_open(void)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
au_write(int d,token_t * tok)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
au_assemble(au_record_t * rec,short event)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
au_teardown(au_record_t * rec)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
au_close(int d,int keep,short event)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
au_close_buffer(int d,short event,u_char * buffer,size_t * buflen)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
au_close_token(token_t * tok,u_char * buffer,size_t * buflen)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