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