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