bsm_audit.c revision 185573
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#31 $ 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#include <pthread.h> 52#include <stdlib.h> 53#include <string.h> 54 55/* array of used descriptors */ 56static au_record_t *open_desc_table[MAX_AUDIT_RECORDS]; 57 58/* The current number of active record descriptors */ 59static 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 */ 66static LIST_HEAD(, au_record) audit_free_q; 67 68static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 69 70/* 71 * This call frees a token_t and its internal data. 72 */ 73void 74au_free_token(token_t *tok) 75{ 76 77 if (tok != NULL) { 78 if (tok->t_data) 79 free(tok->t_data); 80 free(tok); 81 } 82} 83 84/* 85 * This call reserves memory for the audit record. Memory must be guaranteed 86 * before any auditable event can be generated. The au_record_t structure 87 * maintains a reference to the memory allocated above and also the list of 88 * tokens associated with this record. Descriptors are recyled once the 89 * records are added to the audit trail following au_close(). 90 */ 91int 92au_open(void) 93{ 94 au_record_t *rec = NULL; 95 96 pthread_mutex_lock(&mutex); 97 98 if (audit_rec_count == 0) 99 LIST_INIT(&audit_free_q); 100 101 /* 102 * Find an unused descriptor, remove it from the free list, mark as 103 * used. 104 */ 105 if (!LIST_EMPTY(&audit_free_q)) { 106 rec = LIST_FIRST(&audit_free_q); 107 rec->used = 1; 108 LIST_REMOVE(rec, au_rec_q); 109 } 110 111 pthread_mutex_unlock(&mutex); 112 113 if (rec == NULL) { 114 /* 115 * Create a new au_record_t if no descriptors are available. 116 */ 117 rec = malloc (sizeof(au_record_t)); 118 if (rec == NULL) 119 return (-1); 120 121 rec->data = malloc (MAX_AUDIT_RECORD_SIZE * sizeof(u_char)); 122 if (rec->data == NULL) { 123 free(rec); 124 errno = ENOMEM; 125 return (-1); 126 } 127 128 pthread_mutex_lock(&mutex); 129 130 if (audit_rec_count == MAX_AUDIT_RECORDS) { 131 pthread_mutex_unlock(&mutex); 132 free(rec->data); 133 free(rec); 134 135 /* XXX We need to increase size of MAX_AUDIT_RECORDS */ 136 errno = ENOMEM; 137 return (-1); 138 } 139 rec->desc = audit_rec_count; 140 open_desc_table[audit_rec_count] = rec; 141 audit_rec_count++; 142 143 pthread_mutex_unlock(&mutex); 144 145 } 146 147 memset(rec->data, 0, MAX_AUDIT_RECORD_SIZE); 148 149 TAILQ_INIT(&rec->token_q); 150 rec->len = 0; 151 rec->used = 1; 152 153 return (rec->desc); 154} 155 156/* 157 * Store the token with the record descriptor. 158 * 159 * Don't permit writing more to the buffer than would let the trailer be 160 * appended later. 161 */ 162int 163au_write(int d, token_t *tok) 164{ 165 au_record_t *rec; 166 167 if (tok == NULL) { 168 errno = EINVAL; 169 return (-1); /* Invalid Token */ 170 } 171 172 /* Write the token to the record descriptor */ 173 rec = open_desc_table[d]; 174 if ((rec == NULL) || (rec->used == 0)) { 175 errno = EINVAL; 176 return (-1); /* Invalid descriptor */ 177 } 178 179 if (rec->len + tok->len + AUDIT_TRAILER_SIZE > MAX_AUDIT_RECORD_SIZE) { 180 errno = ENOMEM; 181 return (-1); 182 } 183 184 /* Add the token to the tail */ 185 /* 186 * XXX Not locking here -- we should not be writing to 187 * XXX the same descriptor from different threads 188 */ 189 TAILQ_INSERT_TAIL(&rec->token_q, tok, tokens); 190 191 rec->len += tok->len; /* grow record length by token size bytes */ 192 193 /* Token should not be available after this call */ 194 tok = NULL; 195 return (0); /* Success */ 196} 197 198/* 199 * Assemble an audit record out of its tokens, including allocating header and 200 * trailer tokens. Does not free the token chain, which must be done by the 201 * caller if desirable. 202 * 203 * XXX: Assumes there is sufficient space for the header and trailer. 204 */ 205static int 206au_assemble(au_record_t *rec, short event) 207{ 208 token_t *header, *tok, *trailer; 209 size_t tot_rec_size, hdrsize; 210 u_char *dptr; 211 struct in6_addr *aptr; 212 int error; 213 struct auditinfo_addr aia; 214 struct timeval tm; 215 216#ifdef HAVE_AUDIT_SYSCALLS 217 /* 218 * Grab the size of the address family stored in the kernel's audit 219 * state. 220 */ 221 aia.ai_termid.at_type = AU_IPv4; 222 aia.ai_termid.at_addr[0] = INADDR_ANY; 223 if (auditon(A_GETKAUDIT, &aia, sizeof(aia)) < 0) { 224 if (errno != ENOSYS) 225 return (-1); 226#endif /* HAVE_AUDIT_SYSCALLS */ 227 tot_rec_size = rec->len + AUDIT_HEADER_SIZE + 228 AUDIT_TRAILER_SIZE; 229 header = au_to_header(tot_rec_size, event, 0); 230#ifdef HAVE_AUDIT_SYSCALLS 231 } else { 232 if (gettimeofday(&tm, NULL) < 0) 233 return (-1); 234 switch (aia.ai_termid.at_type) { 235 case AU_IPv4: 236 hdrsize = (aia.ai_termid.at_addr[0] == INADDR_ANY) ? 237 AUDIT_HEADER_SIZE : AUDIT_HEADER_EX_SIZE(&aia); 238 break; 239 case AU_IPv6: 240 aptr = (struct in6_addr *)&aia.ai_termid.at_addr[0]; 241 hdrsize = 242 (IN6_IS_ADDR_UNSPECIFIED(aptr)) ? 243 AUDIT_HEADER_SIZE : AUDIT_HEADER_EX_SIZE(&aia); 244 break; 245 } 246 tot_rec_size = rec->len + hdrsize + AUDIT_TRAILER_SIZE; 247 /* 248 * A header size greater then AUDIT_HEADER_SIZE means 249 * that we are using an extended header. 250 */ 251 if (hdrsize > AUDIT_HEADER_SIZE) 252 header = au_to_header32_ex_tm(tot_rec_size, event, 253 0, tm, &aia); 254 else 255 header = au_to_header(tot_rec_size, event, 0); 256 } 257#endif /* HAVE_AUDIT_SYSCALLS */ 258 if (header == NULL) 259 return (-1); 260 261 trailer = au_to_trailer(tot_rec_size); 262 if (trailer == NULL) { 263 error = errno; 264 au_free_token(header); 265 errno = error; 266 return (-1); 267 } 268 269 TAILQ_INSERT_HEAD(&rec->token_q, header, tokens); 270 TAILQ_INSERT_TAIL(&rec->token_q, trailer, tokens); 271 272 rec->len = tot_rec_size; 273 dptr = rec->data; 274 275 TAILQ_FOREACH(tok, &rec->token_q, tokens) { 276 memcpy(dptr, tok->t_data, tok->len); 277 dptr += tok->len; 278 } 279 280 return (0); 281} 282 283/* 284 * Given a record that is no longer of interest, tear it down and convert to a 285 * free record. 286 */ 287static void 288au_teardown(au_record_t *rec) 289{ 290 token_t *tok; 291 292 /* Free the token list */ 293 while ((tok = TAILQ_FIRST(&rec->token_q)) != NULL) { 294 TAILQ_REMOVE(&rec->token_q, tok, tokens); 295 free(tok->t_data); 296 free(tok); 297 } 298 299 rec->used = 0; 300 rec->len = 0; 301 302 pthread_mutex_lock(&mutex); 303 304 /* Add the record to the freelist tail */ 305 LIST_INSERT_HEAD(&audit_free_q, rec, au_rec_q); 306 307 pthread_mutex_unlock(&mutex); 308} 309 310#ifdef HAVE_AUDIT_SYSCALLS 311/* 312 * Add the header token, identify any missing tokens. Write out the tokens to 313 * the record memory and finally, call audit. 314 */ 315int 316au_close(int d, int keep, short event) 317{ 318 au_record_t *rec; 319 size_t tot_rec_size; 320 int retval = 0; 321 322 rec = open_desc_table[d]; 323 if ((rec == NULL) || (rec->used == 0)) { 324 errno = EINVAL; 325 return (-1); /* Invalid descriptor */ 326 } 327 328 if (keep == AU_TO_NO_WRITE) { 329 retval = 0; 330 goto cleanup; 331 } 332 333 tot_rec_size = rec->len + MAX_AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE; 334 335 if (tot_rec_size > MAX_AUDIT_RECORD_SIZE) { 336 /* 337 * XXXRW: Since au_write() is supposed to prevent this, spew 338 * an error here. 339 */ 340 fprintf(stderr, "au_close failed"); 341 errno = ENOMEM; 342 retval = -1; 343 goto cleanup; 344 } 345 346 if (au_assemble(rec, event) < 0) { 347 /* 348 * XXXRW: This is also not supposed to happen, but might if we 349 * are unable to allocate header and trailer memory. 350 */ 351 retval = -1; 352 goto cleanup; 353 } 354 355 /* Call the kernel interface to audit */ 356 retval = audit(rec->data, rec->len); 357 358cleanup: 359 /* CLEANUP */ 360 au_teardown(rec); 361 return (retval); 362} 363#endif /* HAVE_AUDIT_SYSCALLS */ 364 365/* 366 * au_close(), except onto an in-memory buffer. Buffer size as an argument, 367 * record size returned via same argument on success. 368 */ 369int 370au_close_buffer(int d, short event, u_char *buffer, size_t *buflen) 371{ 372 size_t tot_rec_size; 373 au_record_t *rec; 374 int retval; 375 376 rec = open_desc_table[d]; 377 if ((rec == NULL) || (rec->used == 0)) { 378 errno = EINVAL; 379 return (-1); 380 } 381 382 retval = 0; 383 tot_rec_size = rec->len + MAX_AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE; 384 if ((tot_rec_size > MAX_AUDIT_RECORD_SIZE) || 385 (tot_rec_size > *buflen)) { 386 /* 387 * XXXRW: See au_close() comment. 388 */ 389 fprintf(stderr, "au_close_buffer failed %zd", tot_rec_size); 390 errno = ENOMEM; 391 retval = -1; 392 goto cleanup; 393 } 394 395 if (au_assemble(rec, event) < 0) { 396 /* XXXRW: See au_close() comment. */ 397 retval = -1; 398 goto cleanup; 399 } 400 401 memcpy(buffer, rec->data, rec->len); 402 *buflen = rec->len; 403 404cleanup: 405 au_teardown(rec); 406 return (retval); 407} 408 409/* 410 * au_close_token() returns the byte format of a token_t. This won't 411 * generally be used by applications, but is quite useful for writing test 412 * tools. Will free the token on either success or failure. 413 */ 414int 415au_close_token(token_t *tok, u_char *buffer, size_t *buflen) 416{ 417 418 if (tok->len > *buflen) { 419 au_free_token(tok); 420 errno = ENOMEM; 421 return (EINVAL); 422 } 423 424 memcpy(buffer, tok->t_data, tok->len); 425 *buflen = tok->len; 426 au_free_token(tok); 427 return (0); 428} 429