bsm_audit.c revision 186647
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#34 $ 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 */ 58static au_record_t *open_desc_table[MAX_AUDIT_RECORDS]; 59 60/* The current number of active record descriptors */ 61static 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 */ 68static LIST_HEAD(, au_record) audit_free_q; 69 70#ifdef HAVE_PTHREAD_MUTEX_LOCK 71static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 72#endif 73 74/* 75 * This call frees a token_t and its internal data. 76 */ 77void 78au_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 */ 95int 96au_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 */ 176int 177au_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 */ 219static int 220au_assemble(au_record_t *rec, short event) 221{ 222 token_t *header, *tok, *trailer; 223 size_t tot_rec_size, hdrsize; 224 u_char *dptr; 225 struct in6_addr *aptr; 226 int error; 227 struct auditinfo_addr aia; 228 struct timeval tm; 229 230#ifdef HAVE_AUDIT_SYSCALLS 231 /* 232 * Grab the size of the address family stored in the kernel's audit 233 * state. 234 */ 235 aia.ai_termid.at_type = AU_IPv4; 236 aia.ai_termid.at_addr[0] = INADDR_ANY; 237 if (auditon(A_GETKAUDIT, &aia, sizeof(aia)) < 0) { 238 if (errno != ENOSYS && errno != EPERM) 239 return (-1); 240#endif /* HAVE_AUDIT_SYSCALLS */ 241 tot_rec_size = rec->len + AUDIT_HEADER_SIZE + 242 AUDIT_TRAILER_SIZE; 243 header = au_to_header(tot_rec_size, event, 0); 244#ifdef HAVE_AUDIT_SYSCALLS 245 } else { 246 if (gettimeofday(&tm, NULL) < 0) 247 return (-1); 248 switch (aia.ai_termid.at_type) { 249 case AU_IPv4: 250 hdrsize = (aia.ai_termid.at_addr[0] == INADDR_ANY) ? 251 AUDIT_HEADER_SIZE : AUDIT_HEADER_EX_SIZE(&aia); 252 break; 253 case AU_IPv6: 254 aptr = (struct in6_addr *)&aia.ai_termid.at_addr[0]; 255 hdrsize = 256 (IN6_IS_ADDR_UNSPECIFIED(aptr)) ? 257 AUDIT_HEADER_SIZE : AUDIT_HEADER_EX_SIZE(&aia); 258 break; 259 default: 260 return (-1); 261 } 262 tot_rec_size = rec->len + hdrsize + AUDIT_TRAILER_SIZE; 263 /* 264 * A header size greater then AUDIT_HEADER_SIZE means 265 * that we are using an extended header. 266 */ 267 if (hdrsize > AUDIT_HEADER_SIZE) 268 header = au_to_header32_ex_tm(tot_rec_size, event, 269 0, tm, &aia); 270 else 271 header = au_to_header(tot_rec_size, event, 0); 272 } 273#endif /* HAVE_AUDIT_SYSCALLS */ 274 if (header == NULL) 275 return (-1); 276 277 trailer = au_to_trailer(tot_rec_size); 278 if (trailer == NULL) { 279 error = errno; 280 au_free_token(header); 281 errno = error; 282 return (-1); 283 } 284 285 TAILQ_INSERT_HEAD(&rec->token_q, header, tokens); 286 TAILQ_INSERT_TAIL(&rec->token_q, trailer, tokens); 287 288 rec->len = tot_rec_size; 289 dptr = rec->data; 290 291 TAILQ_FOREACH(tok, &rec->token_q, tokens) { 292 memcpy(dptr, tok->t_data, tok->len); 293 dptr += tok->len; 294 } 295 296 return (0); 297} 298 299/* 300 * Given a record that is no longer of interest, tear it down and convert to a 301 * free record. 302 */ 303static void 304au_teardown(au_record_t *rec) 305{ 306 token_t *tok; 307 308 /* Free the token list */ 309 while ((tok = TAILQ_FIRST(&rec->token_q)) != NULL) { 310 TAILQ_REMOVE(&rec->token_q, tok, tokens); 311 free(tok->t_data); 312 free(tok); 313 } 314 315 rec->used = 0; 316 rec->len = 0; 317 318#ifdef HAVE_PTHREAD_MUTEX_LOCK 319 pthread_mutex_lock(&mutex); 320#endif 321 322 /* Add the record to the freelist tail */ 323 LIST_INSERT_HEAD(&audit_free_q, rec, au_rec_q); 324 325#ifdef HAVE_PTHREAD_MUTEX_LOCK 326 pthread_mutex_unlock(&mutex); 327#endif 328} 329 330#ifdef HAVE_AUDIT_SYSCALLS 331/* 332 * Add the header token, identify any missing tokens. Write out the tokens to 333 * the record memory and finally, call audit. 334 */ 335int 336au_close(int d, int keep, short event) 337{ 338 au_record_t *rec; 339 size_t tot_rec_size; 340 int retval = 0; 341 342 rec = open_desc_table[d]; 343 if ((rec == NULL) || (rec->used == 0)) { 344 errno = EINVAL; 345 return (-1); /* Invalid descriptor */ 346 } 347 348 if (keep == AU_TO_NO_WRITE) { 349 retval = 0; 350 goto cleanup; 351 } 352 353 tot_rec_size = rec->len + MAX_AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE; 354 355 if (tot_rec_size > MAX_AUDIT_RECORD_SIZE) { 356 /* 357 * XXXRW: Since au_write() is supposed to prevent this, spew 358 * an error here. 359 */ 360 fprintf(stderr, "au_close failed"); 361 errno = ENOMEM; 362 retval = -1; 363 goto cleanup; 364 } 365 366 if (au_assemble(rec, event) < 0) { 367 /* 368 * XXXRW: This is also not supposed to happen, but might if we 369 * are unable to allocate header and trailer memory. 370 */ 371 retval = -1; 372 goto cleanup; 373 } 374 375 /* Call the kernel interface to audit */ 376 retval = audit(rec->data, rec->len); 377 378cleanup: 379 /* CLEANUP */ 380 au_teardown(rec); 381 return (retval); 382} 383#endif /* HAVE_AUDIT_SYSCALLS */ 384 385/* 386 * au_close(), except onto an in-memory buffer. Buffer size as an argument, 387 * record size returned via same argument on success. 388 */ 389int 390au_close_buffer(int d, short event, u_char *buffer, size_t *buflen) 391{ 392 size_t tot_rec_size; 393 au_record_t *rec; 394 int retval; 395 396 rec = open_desc_table[d]; 397 if ((rec == NULL) || (rec->used == 0)) { 398 errno = EINVAL; 399 return (-1); 400 } 401 402 retval = 0; 403 tot_rec_size = rec->len + MAX_AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE; 404 if ((tot_rec_size > MAX_AUDIT_RECORD_SIZE) || 405 (tot_rec_size > *buflen)) { 406 /* 407 * XXXRW: See au_close() comment. 408 */ 409 fprintf(stderr, "au_close_buffer failed %zd", tot_rec_size); 410 errno = ENOMEM; 411 retval = -1; 412 goto cleanup; 413 } 414 415 if (au_assemble(rec, event) < 0) { 416 /* XXXRW: See au_close() comment. */ 417 retval = -1; 418 goto cleanup; 419 } 420 421 memcpy(buffer, rec->data, rec->len); 422 *buflen = rec->len; 423 424cleanup: 425 au_teardown(rec); 426 return (retval); 427} 428 429/* 430 * au_close_token() returns the byte format of a token_t. This won't 431 * generally be used by applications, but is quite useful for writing test 432 * tools. Will free the token on either success or failure. 433 */ 434int 435au_close_token(token_t *tok, u_char *buffer, size_t *buflen) 436{ 437 438 if (tok->len > *buflen) { 439 au_free_token(tok); 440 errno = ENOMEM; 441 return (EINVAL); 442 } 443 444 memcpy(buffer, tok->t_data, tok->len); 445 *buflen = tok->len; 446 au_free_token(tok); 447 return (0); 448} 449